24
24
25
25
import java .security .spec .PKCS8EncodedKeySpec ;
26
26
import java .util .Base64 ;
27
+
27
28
import org .junit .After ;
28
29
import org .junit .Before ;
29
30
import org .junit .Test ;
31
+ import org .openqa .selenium .InvalidArgumentException ;
30
32
import org .openqa .selenium .JavascriptExecutor ;
31
33
import org .openqa .selenium .environment .webserver .Page ;
32
34
import org .openqa .selenium .testing .JUnit4TestBase ;
39
41
public class VirtualAuthenticatorTest extends JUnit4TestBase {
40
42
41
43
/**
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.
43
45
*/
44
46
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==" ;
48
65
49
66
private final static PKCS8EncodedKeySpec privateKey =
50
- new PKCS8EncodedKeySpec (Base64 .getUrlDecoder ().decode (base64EncodedPK ));
67
+ new PKCS8EncodedKeySpec (Base64 .getMimeDecoder ().decode (base64EncodedPK ));
51
68
52
69
private final static String script =
53
- "async function registerCredential(options = {}) {"
70
+ "async function registerCredential(options = {}) {"
54
71
+ " options = Object.assign({"
55
72
+ " authenticatorSelection: {"
56
73
+ " requireResidentKey: false,"
@@ -114,22 +131,39 @@ public void setup() {
114
131
assumeThat (driver ).isInstanceOf (HasVirtualAuthenticator .class );
115
132
jsAwareDriver = (JavascriptExecutor ) driver ;
116
133
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 );
119
150
}
120
151
121
- private void createSimpleU2FAuthenticator () {
152
+ private void createRKEnabledCTAP2Authenticator () {
122
153
VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions ()
123
- .setProtocol (Protocol .U2F );
154
+ .setProtocol (Protocol .CTAP2 )
155
+ .setHasResidentKey (true )
156
+ .setHasUserVerification (true )
157
+ .setIsUserVerified (true );
124
158
authenticator = ((HasVirtualAuthenticator ) driver ).addVirtualAuthenticator (options );
125
159
}
126
160
127
- private void createRKEnabledAuthenticator () {
161
+ private void createRKDisabledCTAP2Authenticator () {
128
162
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 );
133
167
authenticator = ((HasVirtualAuthenticator ) driver ).addVirtualAuthenticator (options );
134
168
}
135
169
@@ -139,8 +173,9 @@ private void createRKEnabledAuthenticator() {
139
173
*/
140
174
private byte [] convertListIntoArrayOfBytes (List <Long > list ) {
141
175
byte [] ret = new byte [list .size ()];
142
- for (int i = 0 ; i < list .size (); ++i )
176
+ for (int i = 0 ; i < list .size (); ++i ) {
143
177
ret [i ] = list .get (i ).byteValue ();
178
+ }
144
179
return ret ;
145
180
}
146
181
@@ -160,7 +195,7 @@ private String extractIdFrom(Object response) {
160
195
161
196
private Object getAssertionFor (Object credentialId ) {
162
197
return jsAwareDriver .executeAsyncScript (
163
- "getCredential([{"
198
+ "getCredential([{"
164
199
+ " \" type\" : \" public-key\" ,"
165
200
+ " \" id\" : Int8Array.from(arguments[0]),"
166
201
+ "}]).then(arguments[arguments.length - 1]);" , credentialId );
@@ -176,16 +211,16 @@ public void tearDown() {
176
211
@ Test
177
212
public void testCreateAuthenticator () {
178
213
// Register a credential on the Virtual Authenticator.
179
- createSimpleU2FAuthenticator ();
214
+ createRKDisabledU2FAuthenticator ();
180
215
Object response = jsAwareDriver .executeAsyncScript (
181
- "registerCredential().then(arguments[arguments.length - 1]);" );
216
+ "registerCredential().then(arguments[arguments.length - 1]);" );
182
217
assertThat (response ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
183
218
184
219
// Attempt to use the credential to get an assertion.
185
220
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" );
189
224
}
190
225
191
226
@ Test
@@ -200,10 +235,37 @@ public void testRemoveAuthenticator() {
200
235
@ Test
201
236
public void testAddNonResidentCredential () {
202
237
// Add a non-resident credential using the testing API.
203
- createSimpleU2FAuthenticator ();
238
+ createRKDisabledCTAP2Authenticator ();
204
239
byte [] credentialId = {1 , 2 , 3 , 4 };
205
240
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 );
207
269
authenticator .addCredential (credential );
208
270
209
271
// Attempt to use the credential to generate an assertion.
@@ -214,36 +276,59 @@ public void testAddNonResidentCredential() {
214
276
@ Test
215
277
public void testAddResidentCredential () {
216
278
// Add a resident credential using the testing API.
217
- createRKEnabledAuthenticator ();
279
+ createRKEnabledCTAP2Authenticator ();
218
280
byte [] credentialId = {1 , 2 , 3 , 4 };
219
281
byte [] userHandle = {1 };
220
282
Credential credential = Credential .createResidentCredential (
221
- credentialId , "localhost" , privateKey , userHandle , /*signCount=*/ 0 );
283
+ credentialId , "localhost" , privateKey , userHandle , /*signCount=*/ 0 );
222
284
authenticator .addCredential (credential );
223
285
224
286
// Attempt to use the credential to generate an assertion. Notice we use an
225
287
// empty allowCredentials array.
226
288
Object response = jsAwareDriver .executeAsyncScript (
227
- "getCredential([]).then(arguments[arguments.length - 1]);" );
289
+ "getCredential([]).then(arguments[arguments.length - 1]);" );
228
290
229
291
assertThat (response ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
230
292
assertThat (response ).extracting ("attestation.userHandle" ).asList ().containsExactly (1L );
231
293
}
232
294
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
+
233
318
@ Test
234
319
public void testGetCredentials () {
235
320
// Create an authenticator and add two credentials.
236
- createRKEnabledAuthenticator ();
321
+ createRKEnabledCTAP2Authenticator ();
237
322
238
323
// Register a resident credential.
239
324
Object response1 = jsAwareDriver .executeAsyncScript (
240
- "registerCredential({authenticatorSelection: {requireResidentKey: true}})"
325
+ "registerCredential({authenticatorSelection: {requireResidentKey: true}})"
241
326
+ " .then(arguments[arguments.length - 1]);" );
242
327
assertThat (response1 ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
243
328
244
329
// Register a non resident credential.
245
330
Object response2 = jsAwareDriver .executeAsyncScript (
246
- "registerCredential().then(arguments[arguments.length - 1]);" );
331
+ "registerCredential().then(arguments[arguments.length - 1]);" );
247
332
assertThat (response2 ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
248
333
249
334
byte [] credential1Id = convertListIntoArrayOfBytes (extractRawIdFrom (response1 ));
@@ -270,7 +355,7 @@ public void testGetCredentials() {
270
355
assertThat (credential1 .isResidentCredential ()).isTrue ();
271
356
assertThat (credential1 .getPrivateKey ()).isNotNull ();
272
357
assertThat (credential1 .getRpId ()).isEqualTo ("localhost" );
273
- assertThat (credential1 .getUserHandle ()).isEqualTo (new byte [] {1 });
358
+ assertThat (credential1 .getUserHandle ()).isEqualTo (new byte []{1 });
274
359
assertThat (credential1 .getSignCount ()).isEqualTo (1 );
275
360
276
361
assertThat (credential2 .isResidentCredential ()).isFalse ();
@@ -283,11 +368,11 @@ public void testGetCredentials() {
283
368
284
369
@ Test
285
370
public void testRemoveCredentialByRawId () {
286
- createSimpleU2FAuthenticator ();
371
+ createRKDisabledU2FAuthenticator ();
287
372
288
373
// Register credential.
289
374
Object response = jsAwareDriver .executeAsyncScript (
290
- "registerCredential().then(arguments[arguments.length - 1]);" );
375
+ "registerCredential().then(arguments[arguments.length - 1]);" );
291
376
assertThat (response ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
292
377
293
378
// Remove a credential by its ID as an array of bytes.
@@ -297,16 +382,17 @@ public void testRemoveCredentialByRawId() {
297
382
298
383
// Trying to get an assertion should fail.
299
384
response = getAssertionFor (rawId );
300
- assertThat (response ).asInstanceOf (MAP ).extracting ("status" ).asString ().startsWith ("NotAllowedError" );
385
+ assertThat (response ).asInstanceOf (MAP ).extracting ("status" ).asString ()
386
+ .startsWith ("NotAllowedError" );
301
387
}
302
388
303
389
@ Test
304
390
public void testRemoveCredentialByBase64UrlId () {
305
- createSimpleU2FAuthenticator ();
391
+ createRKDisabledU2FAuthenticator ();
306
392
307
393
// Register credential.
308
394
Object response = jsAwareDriver .executeAsyncScript (
309
- "registerCredential().then(arguments[arguments.length - 1]);" );
395
+ "registerCredential().then(arguments[arguments.length - 1]);" );
310
396
assertThat (response ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
311
397
List <Long > rawId = extractRawIdFrom (response );
312
398
@@ -316,21 +402,22 @@ public void testRemoveCredentialByBase64UrlId() {
316
402
317
403
// Trying to get an assertion should fail.
318
404
response = getAssertionFor (rawId );
319
- assertThat (response ).asInstanceOf (MAP ).extracting ("status" ).asString ().startsWith ("NotAllowedError" );
405
+ assertThat (response ).asInstanceOf (MAP ).extracting ("status" ).asString ()
406
+ .startsWith ("NotAllowedError" );
320
407
}
321
408
322
409
@ Test
323
410
public void testRemoveAllCredentials () {
324
- createSimpleU2FAuthenticator ();
411
+ createRKDisabledU2FAuthenticator ();
325
412
326
413
// Register two credentials.
327
414
Object response1 = jsAwareDriver .executeAsyncScript (
328
- "registerCredential().then(arguments[arguments.length - 1]);" );
415
+ "registerCredential().then(arguments[arguments.length - 1]);" );
329
416
assertThat (response1 ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
330
417
List <Long > rawId1 = extractRawIdFrom (response1 );
331
418
332
419
Object response2 = jsAwareDriver .executeAsyncScript (
333
- "registerCredential().then(arguments[arguments.length - 1]);" );
420
+ "registerCredential().then(arguments[arguments.length - 1]);" );
334
421
assertThat (response2 ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
335
422
List <Long > rawId2 = extractRawIdFrom (response1 );
336
423
@@ -339,47 +426,49 @@ public void testRemoveAllCredentials() {
339
426
340
427
// Trying to get an assertion allowing for any of both should fail.
341
428
Object response = jsAwareDriver .executeAsyncScript (
342
- "getCredential([{"
429
+ "getCredential([{"
343
430
+ " \" type\" : \" public-key\" ,"
344
431
+ " \" id\" : Int8Array.from(arguments[0]),"
345
432
+ "}, {"
346
433
+ " \" type\" : \" public-key\" ,"
347
434
+ " \" id\" : Int8Array.from(arguments[1]),"
348
435
+ "}]).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" );
351
439
}
352
440
353
441
@ Test
354
442
public void testSetUserVerified () {
355
- createRKEnabledAuthenticator ();
443
+ createRKEnabledCTAP2Authenticator ();
356
444
357
445
// Register a credential requiring UV.
358
446
Object response = jsAwareDriver .executeAsyncScript (
359
- "registerCredential({authenticatorSelection: {userVerification: 'required'}})"
447
+ "registerCredential({authenticatorSelection: {userVerification: 'required'}})"
360
448
+ " .then(arguments[arguments.length - 1]);" );
361
449
assertThat (response ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
362
450
List <Long > rawId = extractRawIdFrom (response );
363
451
364
452
// Getting an assertion requiring user verification should succeed.
365
453
response = jsAwareDriver .executeAsyncScript (
366
- "getCredential([{"
454
+ "getCredential([{"
367
455
+ " \" type\" : \" public-key\" ,"
368
456
+ " \" id\" : Int8Array.from(arguments[0]),"
369
457
+ "}], {userVerification: 'required'}).then(arguments[arguments.length - 1]);" ,
370
- rawId );
458
+ rawId );
371
459
assertThat (response ).asInstanceOf (MAP ).containsEntry ("status" , "OK" );
372
460
373
461
// Disable user verification.
374
462
authenticator .setUserVerified (false );
375
463
376
464
// Getting an assertion requiring user verification should fail.
377
465
response = jsAwareDriver .executeAsyncScript (
378
- "getCredential([{"
466
+ "getCredential([{"
379
467
+ " \" type\" : \" public-key\" ,"
380
468
+ " \" id\" : Int8Array.from(arguments[0]),"
381
469
+ "}], {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" );
384
473
}
385
474
}
0 commit comments