2424
2525import java .security .spec .PKCS8EncodedKeySpec ;
2626import java .util .Base64 ;
27+
2728import org .junit .After ;
2829import org .junit .Before ;
2930import org .junit .Test ;
31+ import org .openqa .selenium .InvalidArgumentException ;
3032import org .openqa .selenium .JavascriptExecutor ;
3133import org .openqa .selenium .environment .webserver .Page ;
3234import org .openqa .selenium .testing .JUnit4TestBase ;
3941public class VirtualAuthenticatorTest extends JUnit4TestBase {
4042
4143 /**
42- * A pkcs#8 encoded unencrypted EC256 private key as a base64url string.
44+ * A pkcs#8 encoded encrypted RSA private key as a base64url string.
4345 */
4446 private final static String base64EncodedPK =
45- "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8_zMDQDYAxlU-Q"
46- + "hk1Dwkf0v18GZca1DMF3SaJ9HPdmShRANCAASNYX5lyVCOZLzFZzrIKmeZ2jwU"
47- + "RmgsJYxGP__fWN_S-j5sN4tT15XEpN_7QZnt14YvI6uvAgO0uJEboFaZlOEB" ;
47+ "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDbBOu5Lhs4vpowbCnmCyLUpIE7JM9sm9QXzye2G+jr+Kr"
48+ + "MsinWohEce47BFPJlTaDzHSvOW2eeunBO89ZcvvVc8RLz4qyQ8rO98xS1jtgqi1NcBPETDrtzthODu/gd0sjB2Tk3TLuB"
49+ + "GVoPXt54a+Oo4JbBJ6h3s0+5eAfGplCbSNq6hN3Jh9YOTw5ZA6GCEy5l8zBaOgjXytd2v2OdSVoEDNiNQRkjJd2rmS2oi"
50+ + "9AyQFR3B7BrPSiDlCcITZFOWgLF5C31Wp/PSHwQhlnh7/6YhnE2y9tzsUvzx0wJXrBADW13+oMxrneDK3WGbxTNYgIi1P"
51+ + "vSqXlqGjHtCK+R2QkXAgMBAAECggEAVc6bu7VAnP6v0gDOeX4razv4FX/adCao9ZsHZ+WPX8PQxtmWYqykH5CY4TSfsui"
52+ + "zAgyPuQ0+j4Vjssr9VODLqFoanspT6YXsvaKanncUYbasNgUJnfnLnw3an2XpU2XdmXTNYckCPRX9nsAAURWT3/n9ljc/"
53+ + "XYY22ecYxM8sDWnHu2uKZ1B7M3X60bQYL5T/lVXkKdD6xgSNLeP4AkRx0H4egaop68hoW8FIwmDPVWYVAvo8etzWCtib"
54+ + "RXz5FcNld9MgD/Ai7ycKy4Q1KhX5GBFI79MVVaHkSQfxPHpr7/XcmpQOEAr+BMPon4s4vnKqAGdGB3j/E3d/+4F2swyko"
55+ + "QKBgQD8hCsp6FIQ5umJlk9/j/nGsMl85LgLaNVYpWlPRKPc54YNumtvj5vx1BG+zMbT7qIE3nmUPTCHP7qb5ERZG4CdMC"
56+ + "S6S64/qzZEqijLCqepwj6j4fV5SyPWEcpxf6ehNdmcfgzVB3Wolfwh1ydhx/96L1jHJcTKchdJJzlfTvq8wwKBgQDeCnK"
57+ + "ws1t5GapfE1rmC/h4olL2qZTth9oQmbrXYohVnoqNFslDa43ePZwL9Jmd9kYb0axOTNMmyrP0NTj41uCfgDS0cJnNTc63"
58+ + "ojKjegxHIyYDKRZNVUR/dxAYB/vPfBYZUS7M89pO6LLsHhzS3qpu3/hppo/Uc/AM /r8PSflNHQKBgDnWgBh6OQncChPUl"
59+ + "OLv9FMZPR1ZOfqLCYrjYEqiuzGm6iKM13zXFO4AGAxu1P/IAd5BovFcTpg79Z8tWqZaUUwvscnl+cRlj+mMXAmdqCeO8V"
60+ + "ASOmqM1ml667axeZDIR867ZG8K5V029Wg+4qtX5uFypNAAi6GfHkxIKrD04yOHAoGACdh4wXESi0oiDdkz3KOHPwIjn6B"
61+ + "hZC7z8mx+pnJODU3cYukxv3WTctlUhAsyjJiQ/0bK1yX87ulqFVgO0Knmh+wNajrb9wiONAJTMICG7tiWJOm7fW5cfTJw"
62+ + "WkBwYADmkfTRmHDvqzQSSvoC2S7aa9QulbC3C/qgGFNrcWgcT9kCgYAZTa1P9bFCDU7hJc2mHwJwAW7/FQKEJg8SL33KI"
63+ + "NpLwcR8fqaYOdAHWWz636osVEqosRrHzJOGpf9x2RSWzQJ+dq8+6fACgfFZOVpN644+sAHfNPAI/gnNKU5OfUv+eav8fB"
64+ + "nzlf1A3y3GIkyMyzFN3DE7e0n/lyqxE4HBYGpI8g==" ;
4865
4966 private final static PKCS8EncodedKeySpec privateKey =
50- new PKCS8EncodedKeySpec (Base64 .getUrlDecoder ().decode (base64EncodedPK ));
67+ new PKCS8EncodedKeySpec (Base64 .getMimeDecoder ().decode (base64EncodedPK ));
5168
5269 private final static String script =
53- "async function registerCredential(options = {}) {"
70+ "async function registerCredential(options = {}) {"
5471 + " options = Object.assign({"
5572 + " authenticatorSelection: {"
5673 + " requireResidentKey: false,"
@@ -114,22 +131,39 @@ public void setup() {
114131 assumeThat (driver ).isInstanceOf (HasVirtualAuthenticator .class );
115132 jsAwareDriver = (JavascriptExecutor ) driver ;
116133 driver .get (appServer .create (new Page ()
117- .withTitle ("Virtual Authenticator Test" )
118- .withScripts (script )));
134+ .withTitle ("Virtual Authenticator Test" )
135+ .withScripts (script )));
136+ }
137+
138+ private void createRKEnabledU2FAuthenticator () {
139+ VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions ()
140+ .setProtocol (Protocol .U2F )
141+ .setHasResidentKey (true );
142+ authenticator = ((HasVirtualAuthenticator ) driver ).addVirtualAuthenticator (options );
143+ }
144+
145+ private void createRKDisabledU2FAuthenticator () {
146+ VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions ()
147+ .setProtocol (Protocol .U2F )
148+ .setHasResidentKey (false );
149+ authenticator = ((HasVirtualAuthenticator ) driver ).addVirtualAuthenticator (options );
119150 }
120151
121- private void createSimpleU2FAuthenticator () {
152+ private void createRKEnabledCTAP2Authenticator () {
122153 VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions ()
123- .setProtocol (Protocol .U2F );
154+ .setProtocol (Protocol .CTAP2 )
155+ .setHasResidentKey (true )
156+ .setHasUserVerification (true )
157+ .setIsUserVerified (true );
124158 authenticator = ((HasVirtualAuthenticator ) driver ).addVirtualAuthenticator (options );
125159 }
126160
127- private void createRKEnabledAuthenticator () {
161+ private void createRKDisabledCTAP2Authenticator () {
128162 VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions ()
129- .setProtocol (Protocol .CTAP2 )
130- .setHasResidentKey (true )
131- .setHasUserVerification (true )
132- .setIsUserVerified (true );
163+ .setProtocol (Protocol .CTAP2 )
164+ .setHasResidentKey (false )
165+ .setHasUserVerification (true )
166+ .setIsUserVerified (true );
133167 authenticator = ((HasVirtualAuthenticator ) driver ).addVirtualAuthenticator (options );
134168 }
135169
@@ -139,8 +173,9 @@ private void createRKEnabledAuthenticator() {
139173 */
140174 private byte [] convertListIntoArrayOfBytes (List <Long > list ) {
141175 byte [] ret = new byte [list .size ()];
142- for (int i = 0 ; i < list .size (); ++i )
176+ for (int i = 0 ; i < list .size (); ++i ) {
143177 ret [i ] = list .get (i ).byteValue ();
178+ }
144179 return ret ;
145180 }
146181
@@ -160,7 +195,7 @@ private String extractIdFrom(Object response) {
160195
161196 private Object getAssertionFor (Object credentialId ) {
162197 return jsAwareDriver .executeAsyncScript (
163- "getCredential([{"
198+ "getCredential([{"
164199 + " \" type\" : \" public-key\" ,"
165200 + " \" id\" : Int8Array.from(arguments[0]),"
166201 + "}]).then(arguments[arguments.length - 1]);" , credentialId );
@@ -176,16 +211,16 @@ public void tearDown() {
176211 @ Test
177212 public void testCreateAuthenticator () {
178213 // Register a credential on the Virtual Authenticator.
179- createSimpleU2FAuthenticator ();
214+ createRKDisabledU2FAuthenticator ();
180215 Object response = jsAwareDriver .executeAsyncScript (
181- "registerCredential().then(arguments[arguments.length - 1]);" );
216+ "registerCredential().then(arguments[arguments.length - 1]);" );
182217 assertThat (response ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
183218
184219 // Attempt to use the credential to get an assertion.
185220 assertThat (response )
186- .extracting ("credential.rawId" )
187- .extracting (this ::getAssertionFor ).asInstanceOf (MAP )
188- .containsEntry ("status" , "OK" );
221+ .extracting ("credential.rawId" )
222+ .extracting (this ::getAssertionFor ).asInstanceOf (MAP )
223+ .containsEntry ("status" , "OK" );
189224 }
190225
191226 @ Test
@@ -200,10 +235,37 @@ public void testRemoveAuthenticator() {
200235 @ Test
201236 public void testAddNonResidentCredential () {
202237 // Add a non-resident credential using the testing API.
203- createSimpleU2FAuthenticator ();
238+ createRKDisabledCTAP2Authenticator ();
204239 byte [] credentialId = {1 , 2 , 3 , 4 };
205240 Credential credential = Credential .createNonResidentCredential (
206- credentialId , "localhost" , privateKey , /*signCount=*/ 0 );
241+ credentialId , "localhost" , privateKey , /*signCount=*/ 0 );
242+ authenticator .addCredential (credential );
243+
244+ // Attempt to use the credential to generate an assertion.
245+ Object response = getAssertionFor (Arrays .asList (1 , 2 , 3 , 4 ));
246+ assertThat (response ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
247+ }
248+
249+ @ Test
250+ public void testAddNonResidentCredentialWhenAuthenticatorUsesU2FProtocol () {
251+ // Add a non-resident credential using the testing API.
252+
253+ createRKDisabledU2FAuthenticator ();
254+
255+ /**
256+ * A pkcs#8 encoded unencrypted EC256 private key as a base64url string.
257+ */
258+ String base64EncodedPK =
259+ "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8_zMDQDYAxlU-Q"
260+ + "hk1Dwkf0v18GZca1DMF3SaJ9HPdmShRANCAASNYX5lyVCOZLzFZzrIKmeZ2jwU"
261+ + "RmgsJYxGP__fWN_S-j5sN4tT15XEpN_7QZnt14YvI6uvAgO0uJEboFaZlOEB" ;
262+
263+ PKCS8EncodedKeySpec privateKey =
264+ new PKCS8EncodedKeySpec (Base64 .getUrlDecoder ().decode (base64EncodedPK ));
265+
266+ byte [] credentialId = {1 , 2 , 3 , 4 };
267+ Credential credential = Credential .createNonResidentCredential (
268+ credentialId , "localhost" , privateKey , /*signCount=*/ 0 );
207269 authenticator .addCredential (credential );
208270
209271 // Attempt to use the credential to generate an assertion.
@@ -214,36 +276,59 @@ public void testAddNonResidentCredential() {
214276 @ Test
215277 public void testAddResidentCredential () {
216278 // Add a resident credential using the testing API.
217- createRKEnabledAuthenticator ();
279+ createRKEnabledCTAP2Authenticator ();
218280 byte [] credentialId = {1 , 2 , 3 , 4 };
219281 byte [] userHandle = {1 };
220282 Credential credential = Credential .createResidentCredential (
221- credentialId , "localhost" , privateKey , userHandle , /*signCount=*/ 0 );
283+ credentialId , "localhost" , privateKey , userHandle , /*signCount=*/ 0 );
222284 authenticator .addCredential (credential );
223285
224286 // Attempt to use the credential to generate an assertion. Notice we use an
225287 // empty allowCredentials array.
226288 Object response = jsAwareDriver .executeAsyncScript (
227- "getCredential([]).then(arguments[arguments.length - 1]);" );
289+ "getCredential([]).then(arguments[arguments.length - 1]);" );
228290
229291 assertThat (response ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
230292 assertThat (response ).extracting ("attestation.userHandle" ).asList ().containsExactly (1L );
231293 }
232294
295+ @ Test (expected = InvalidArgumentException .class )
296+ public void testAddResidentCredentialNotSupportedWhenAuthenticatorUsesU2FProtocol () {
297+ // Add a resident credential using the testing API.
298+ createRKEnabledU2FAuthenticator ();
299+
300+ /**
301+ * A pkcs#8 encoded unencrypted EC256 private key as a base64url string.
302+ */
303+ String base64EncodedPK =
304+ "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8_zMDQDYAxlU-Q"
305+ + "hk1Dwkf0v18GZca1DMF3SaJ9HPdmShRANCAASNYX5lyVCOZLzFZzrIKmeZ2jwU"
306+ + "RmgsJYxGP__fWN_S-j5sN4tT15XEpN_7QZnt14YvI6uvAgO0uJEboFaZlOEB" ;
307+
308+ PKCS8EncodedKeySpec privateKey =
309+ new PKCS8EncodedKeySpec (Base64 .getUrlDecoder ().decode (base64EncodedPK ));
310+
311+ byte [] credentialId = {1 , 2 , 3 , 4 };
312+ byte [] userHandle = {1 };
313+ Credential credential = Credential .createResidentCredential (
314+ credentialId , "localhost" , privateKey , userHandle , /*signCount=*/ 0 );
315+ authenticator .addCredential (credential );
316+ }
317+
233318 @ Test
234319 public void testGetCredentials () {
235320 // Create an authenticator and add two credentials.
236- createRKEnabledAuthenticator ();
321+ createRKEnabledCTAP2Authenticator ();
237322
238323 // Register a resident credential.
239324 Object response1 = jsAwareDriver .executeAsyncScript (
240- "registerCredential({authenticatorSelection: {requireResidentKey: true}})"
325+ "registerCredential({authenticatorSelection: {requireResidentKey: true}})"
241326 + " .then(arguments[arguments.length - 1]);" );
242327 assertThat (response1 ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
243328
244329 // Register a non resident credential.
245330 Object response2 = jsAwareDriver .executeAsyncScript (
246- "registerCredential().then(arguments[arguments.length - 1]);" );
331+ "registerCredential().then(arguments[arguments.length - 1]);" );
247332 assertThat (response2 ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
248333
249334 byte [] credential1Id = convertListIntoArrayOfBytes (extractRawIdFrom (response1 ));
@@ -270,7 +355,7 @@ public void testGetCredentials() {
270355 assertThat (credential1 .isResidentCredential ()).isTrue ();
271356 assertThat (credential1 .getPrivateKey ()).isNotNull ();
272357 assertThat (credential1 .getRpId ()).isEqualTo ("localhost" );
273- assertThat (credential1 .getUserHandle ()).isEqualTo (new byte [] {1 });
358+ assertThat (credential1 .getUserHandle ()).isEqualTo (new byte []{1 });
274359 assertThat (credential1 .getSignCount ()).isEqualTo (1 );
275360
276361 assertThat (credential2 .isResidentCredential ()).isFalse ();
@@ -283,11 +368,11 @@ public void testGetCredentials() {
283368
284369 @ Test
285370 public void testRemoveCredentialByRawId () {
286- createSimpleU2FAuthenticator ();
371+ createRKDisabledU2FAuthenticator ();
287372
288373 // Register credential.
289374 Object response = jsAwareDriver .executeAsyncScript (
290- "registerCredential().then(arguments[arguments.length - 1]);" );
375+ "registerCredential().then(arguments[arguments.length - 1]);" );
291376 assertThat (response ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
292377
293378 // Remove a credential by its ID as an array of bytes.
@@ -297,16 +382,17 @@ public void testRemoveCredentialByRawId() {
297382
298383 // Trying to get an assertion should fail.
299384 response = getAssertionFor (rawId );
300- assertThat (response ).asInstanceOf (MAP ).extracting ("status" ).asString ().startsWith ("NotAllowedError" );
385+ assertThat (response ).asInstanceOf (MAP ).extracting ("status" ).asString ()
386+ .startsWith ("NotAllowedError" );
301387 }
302388
303389 @ Test
304390 public void testRemoveCredentialByBase64UrlId () {
305- createSimpleU2FAuthenticator ();
391+ createRKDisabledU2FAuthenticator ();
306392
307393 // Register credential.
308394 Object response = jsAwareDriver .executeAsyncScript (
309- "registerCredential().then(arguments[arguments.length - 1]);" );
395+ "registerCredential().then(arguments[arguments.length - 1]);" );
310396 assertThat (response ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
311397 List <Long > rawId = extractRawIdFrom (response );
312398
@@ -316,21 +402,22 @@ public void testRemoveCredentialByBase64UrlId() {
316402
317403 // Trying to get an assertion should fail.
318404 response = getAssertionFor (rawId );
319- assertThat (response ).asInstanceOf (MAP ).extracting ("status" ).asString ().startsWith ("NotAllowedError" );
405+ assertThat (response ).asInstanceOf (MAP ).extracting ("status" ).asString ()
406+ .startsWith ("NotAllowedError" );
320407 }
321408
322409 @ Test
323410 public void testRemoveAllCredentials () {
324- createSimpleU2FAuthenticator ();
411+ createRKDisabledU2FAuthenticator ();
325412
326413 // Register two credentials.
327414 Object response1 = jsAwareDriver .executeAsyncScript (
328- "registerCredential().then(arguments[arguments.length - 1]);" );
415+ "registerCredential().then(arguments[arguments.length - 1]);" );
329416 assertThat (response1 ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
330417 List <Long > rawId1 = extractRawIdFrom (response1 );
331418
332419 Object response2 = jsAwareDriver .executeAsyncScript (
333- "registerCredential().then(arguments[arguments.length - 1]);" );
420+ "registerCredential().then(arguments[arguments.length - 1]);" );
334421 assertThat (response2 ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
335422 List <Long > rawId2 = extractRawIdFrom (response1 );
336423
@@ -339,47 +426,49 @@ public void testRemoveAllCredentials() {
339426
340427 // Trying to get an assertion allowing for any of both should fail.
341428 Object response = jsAwareDriver .executeAsyncScript (
342- "getCredential([{"
429+ "getCredential([{"
343430 + " \" type\" : \" public-key\" ,"
344431 + " \" id\" : Int8Array.from(arguments[0]),"
345432 + "}, {"
346433 + " \" type\" : \" public-key\" ,"
347434 + " \" id\" : Int8Array.from(arguments[1]),"
348435 + "}]).then(arguments[arguments.length - 1]);" ,
349- rawId1 , rawId2 );
350- assertThat (response ).asInstanceOf (MAP ).extracting ("status" ).asString ().startsWith ("NotAllowedError" );
436+ rawId1 , rawId2 );
437+ assertThat (response ).asInstanceOf (MAP ).extracting ("status" ).asString ()
438+ .startsWith ("NotAllowedError" );
351439 }
352440
353441 @ Test
354442 public void testSetUserVerified () {
355- createRKEnabledAuthenticator ();
443+ createRKEnabledCTAP2Authenticator ();
356444
357445 // Register a credential requiring UV.
358446 Object response = jsAwareDriver .executeAsyncScript (
359- "registerCredential({authenticatorSelection: {userVerification: 'required'}})"
447+ "registerCredential({authenticatorSelection: {userVerification: 'required'}})"
360448 + " .then(arguments[arguments.length - 1]);" );
361449 assertThat (response ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
362450 List <Long > rawId = extractRawIdFrom (response );
363451
364452 // Getting an assertion requiring user verification should succeed.
365453 response = jsAwareDriver .executeAsyncScript (
366- "getCredential([{"
454+ "getCredential([{"
367455 + " \" type\" : \" public-key\" ,"
368456 + " \" id\" : Int8Array.from(arguments[0]),"
369457 + "}], {userVerification: 'required'}).then(arguments[arguments.length - 1]);" ,
370- rawId );
458+ rawId );
371459 assertThat (response ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
372460
373461 // Disable user verification.
374462 authenticator .setUserVerified (false );
375463
376464 // Getting an assertion requiring user verification should fail.
377465 response = jsAwareDriver .executeAsyncScript (
378- "getCredential([{"
466+ "getCredential([{"
379467 + " \" type\" : \" public-key\" ,"
380468 + " \" id\" : Int8Array.from(arguments[0]),"
381469 + "}], {userVerification: 'required'}).then(arguments[arguments.length - 1]);" ,
382- rawId );
383- assertThat (response ).asInstanceOf (MAP ).extracting ("status" ).asString ().startsWith ("NotAllowedError" );
470+ rawId );
471+ assertThat (response ).asInstanceOf (MAP ).extracting ("status" ).asString ()
472+ .startsWith ("NotAllowedError" );
384473 }
385474}
0 commit comments