@@ -248,7 +248,7 @@ impl<'de> Deserialize<'de> for LargeBlobArrayElement {
248
248
}
249
249
}
250
250
251
- #[ derive( Default , Debug ) ]
251
+ #[ derive( Default , Debug , PartialEq , Eq ) ]
252
252
pub struct LargeBlobsResponse {
253
253
pub ( crate ) large_blob_array : Vec < LargeBlobArrayElement > ,
254
254
/// Truncated SHA-256 hash of the preceding bytes
@@ -293,20 +293,23 @@ impl<'de> Deserialize<'de> for LargeBlobsResponse {
293
293
) ) ;
294
294
}
295
295
// split off trailing hash-bytes
296
- let ( mut large_blob, hash_slice) = payload. split_at ( payload. len ( ) - 16 ) ;
296
+ let ( mut large_blob, mut hash_slice) =
297
+ payload. split_at ( payload. len ( ) - 16 ) ;
297
298
298
299
let mut hasher = Sha256 :: new ( ) ;
299
300
hasher. update ( large_blob) ;
300
301
let expected_hash = hasher. finalize ( ) ;
301
302
// The initial serialized large-blob array is the value of the serialized large-blob array on a fresh authenticator, as well as immediately after a reset. It is the byte string h'8076be8b528d0075f7aae98d6fa57a6d3c', which is an empty CBOR array (80) followed by LEFT(SHA-256(h'80'), 16).
302
- let default_large_blob = [
303
- 0x80 , 0x76 , 0xbe , 0x8b , 0x52 , 0x8d , 0x00 , 0x75 , 0xf7 , 0xaa , 0xe9 ,
304
- 0x8d , 0x6f , 0xa5 , 0x7a , 0x6d , 0x3c ,
303
+ let default_large_blob = [ 0x80 ] ;
304
+ let default_hash = [
305
+ 0x76 , 0xbe , 0x8b , 0x52 , 0x8d , 0x00 , 0x75 , 0xf7 , 0xaa , 0xe9 , 0x8d ,
306
+ 0x6f , 0xa5 , 0x7a , 0x6d , 0x3c ,
305
307
] ;
306
308
// Once complete, the platform MUST confirm that the embedded SHA-256 hash is correct, based on the definition above. If not, the configuration is corrupt and the platform MUST discard it and act as if the initial serialized large-blob array was received.
307
309
if & expected_hash. as_slice ( ) [ 0 ..16 ] != hash_slice {
308
310
warn ! ( "Large blob array hash doesn't match with the expected value! Assuming an empty array." ) ;
309
311
large_blob = & default_large_blob;
312
+ hash_slice = & default_hash;
310
313
}
311
314
312
315
let byte_len = large_blob. len ( ) as u64 ;
@@ -449,3 +452,226 @@ where
449
452
bytes. extend_from_slice ( & hash[ ..16 ] ) ;
450
453
write_large_blob_segment ( dev, keep_alive, & bytes, 0 , pin_uv_auth_token)
451
454
}
455
+
456
+ #[ cfg( test) ]
457
+ pub mod tests {
458
+ use super :: * ;
459
+ use crate :: consts:: HIDCmd ;
460
+ use crate :: transport:: device_selector:: Device ;
461
+ use crate :: transport:: hid:: HIDDevice ;
462
+ use crate :: transport:: platform:: device:: IN_HID_RPT_SIZE ;
463
+ use crate :: transport:: { FidoDevice , FidoProtocol } ;
464
+ use rand:: { thread_rng, RngCore } ;
465
+
466
+ fn add_bytes_to_read ( cid : & [ u8 ] , bytes : & [ u8 ] , device : & mut Device ) {
467
+ let mut data = Vec :: new ( ) ;
468
+ let payload_len = ( bytes. len ( ) + 1 ) as u16 ;
469
+ // We skip the very first byte (HIDCmd::Cbor), as we will insert it below
470
+ data. extend ( payload_len. to_be_bytes ( ) ) ;
471
+ data. push ( 0x00 ) ; // status == success
472
+ data. extend ( bytes) ;
473
+ let chunks = data. chunks ( IN_HID_RPT_SIZE - 5 ) ;
474
+ for ( id, chunk) in chunks. enumerate ( ) {
475
+ let mut msg = cid. to_vec ( ) ;
476
+ let state_or_seq = if id == 0 {
477
+ HIDCmd :: Cbor . into ( )
478
+ } else {
479
+ ( id - 1 ) as u8 // SEQ
480
+ } ;
481
+ msg. push ( state_or_seq) ;
482
+ msg. extend ( chunk) ;
483
+ device. add_read ( & msg, 0 ) ;
484
+ }
485
+ }
486
+
487
+ #[ test]
488
+ fn test_read_large_blob_array ( ) {
489
+ let keep_alive = || true ;
490
+ let mut device = Device :: new ( "commands/large_blobs" ) . unwrap ( ) ;
491
+ assert_eq ! ( device. get_protocol( ) , FidoProtocol :: CTAP2 ) ;
492
+
493
+ // 'initialize' the device
494
+ let mut cid = [ 0u8 ; 4 ] ;
495
+ thread_rng ( ) . fill_bytes ( & mut cid) ;
496
+ device. set_cid ( cid) ;
497
+
498
+ let cmd = [
499
+ 0xa2 , // map(2)
500
+ 0x01 , // unsigned(1) - get
501
+ 0x19 , 0x03 , 0xc0 , // unsigned(960)
502
+ 0x03 , // unsigned(3) - offset
503
+ 0x00 , // unsigned(0)
504
+ ] ;
505
+ let mut msg = cid. to_vec ( ) ;
506
+ msg. extend ( vec ! [ HIDCmd :: Cbor . into( ) , 0x00 , cmd. len( ) as u8 + 1 ] ) ; // cmd + bcnt
507
+ msg. extend ( vec ! [ 0x0C ] ) ; // LargeBlobs
508
+ msg. extend ( cmd) ; // Actual command
509
+ device. add_write ( & msg, 0 ) ;
510
+
511
+ add_bytes_to_read ( & cid, & LARGE_BLOB_ARRAY , & mut device) ;
512
+ let array = read_large_blob_array ( & mut device, & keep_alive)
513
+ . expect ( "Failed to read large blob array" ) ;
514
+ let expected = get_expected_large_blobs_response ( ) ;
515
+ assert_eq ! ( expected, array) ;
516
+ }
517
+
518
+ #[ test]
519
+ fn test_read_large_blob_array_with_wrong_hash ( ) {
520
+ let keep_alive = || true ;
521
+ let mut device = Device :: new ( "commands/large_blobs" ) . unwrap ( ) ;
522
+ assert_eq ! ( device. get_protocol( ) , FidoProtocol :: CTAP2 ) ;
523
+
524
+ // 'initialize' the device
525
+ let mut cid = [ 0u8 ; 4 ] ;
526
+ thread_rng ( ) . fill_bytes ( & mut cid) ;
527
+ device. set_cid ( cid) ;
528
+
529
+ let cmd = [
530
+ 0xa2 , // map(2)
531
+ 0x01 , // unsigned(1) - get
532
+ 0x19 , 0x03 , 0xc0 , // unsigned(960)
533
+ 0x03 , // unsigned(3) - offset
534
+ 0x00 , // unsigned(0)
535
+ ] ;
536
+ let mut msg = cid. to_vec ( ) ;
537
+ msg. extend ( vec ! [ HIDCmd :: Cbor . into( ) , 0x00 , cmd. len( ) as u8 + 1 ] ) ; // cmd + bcnt
538
+ msg. extend ( vec ! [ 0x0C ] ) ; // LargeBlobs
539
+ msg. extend ( cmd) ; // Actual command
540
+ device. add_write ( & msg, 0 ) ;
541
+
542
+ let mut payload = LARGE_BLOB_ARRAY ;
543
+ payload[ 483 ] += 1 ; // Changing one byte in the hash
544
+
545
+ add_bytes_to_read ( & cid, & payload, & mut device) ;
546
+ // Should succeed, but give us the default empty Large blob array, as defined by the spec
547
+ let array = read_large_blob_array ( & mut device, & keep_alive)
548
+ . expect ( "Failed to read large blob array" ) ;
549
+ let expected = LargeBlobsResponse {
550
+ large_blob_array : vec ! [ ] ,
551
+ hash : [
552
+ 0x76 , 0xbe , 0x8b , 0x52 , 0x8d , 0x00 , 0x75 , 0xf7 , 0xaa , 0xe9 , 0x8d , 0x6f , 0xa5 , 0x7a ,
553
+ 0x6d , 0x3c ,
554
+ ] ,
555
+ byte_len : 1 ,
556
+ } ;
557
+ assert_eq ! ( expected, array) ;
558
+ }
559
+
560
+ #[ test]
561
+ fn test_read_large_blob_array_multi_read ( ) {
562
+ let keep_alive = || true ;
563
+ let mut device = Device :: new ( "commands/large_blobs" ) . unwrap ( ) ;
564
+ assert_eq ! ( device. get_protocol( ) , FidoProtocol :: CTAP2 ) ;
565
+ device. set_authenticator_info ( crate :: AuthenticatorInfo {
566
+ max_msg_size : Some ( 164 ) , // Note: This value minus 64 will be the fragment size
567
+ ..Default :: default ( )
568
+ } ) ;
569
+
570
+ // 'initialize' the device
571
+ let mut cid = [ 0u8 ; 4 ] ;
572
+ thread_rng ( ) . fill_bytes ( & mut cid) ;
573
+ device. set_cid ( cid) ;
574
+
575
+ for ii in 0 ..5 {
576
+ let mut cmd = vec ! [
577
+ 0xa2 , // map(2)
578
+ 0x01 , // unsigned(1) - get
579
+ 0x18 , 0x64 , // unsigned(100)
580
+ 0x03 , // unsigned(3) - offset
581
+ ] ;
582
+ cmd. extend ( & to_vec ( & serde_cbor:: Value :: Integer ( ii * 100 ) ) . unwrap ( ) ) ;
583
+ let mut msg = cid. to_vec ( ) ;
584
+ msg. extend ( vec ! [ HIDCmd :: Cbor . into( ) , 0x00 , cmd. len( ) as u8 + 1 ] ) ; // cmd + bcnt
585
+ msg. extend ( vec ! [ 0x0C ] ) ; // LargeBlobs
586
+ msg. extend ( cmd) ; // Actual command
587
+ device. add_write ( & msg, 0 ) ;
588
+ }
589
+
590
+ for chunk in LARGE_BLOB_ARRAY . chunks ( 100 ) {
591
+ add_bytes_to_read ( & cid, chunk, & mut device) ;
592
+ }
593
+ let array = read_large_blob_array ( & mut device, & keep_alive)
594
+ . expect ( "Failed to read large blob array" ) ;
595
+ let expected = get_expected_large_blobs_response ( ) ;
596
+ assert_eq ! ( expected, array) ;
597
+ }
598
+
599
+ fn get_expected_large_blobs_response ( ) -> LargeBlobsResponse {
600
+ LargeBlobsResponse {
601
+ large_blob_array : vec ! [
602
+ LargeBlobArrayElement {
603
+ ciphertext: vec![
604
+ 116 , 199 , 82 , 206 , 68 , 131 , 237 , 242 , 213 , 144 , 244 , 185 , 155 , 148 , 217 ,
605
+ 62 , 245 , 5 , 128 , 162 , 176 , 99 , 5 , 160 , 186 , 68 , 88 , 140 , 38 , 255 , 168 , 254 ,
606
+ 88 , 161 , 188 , 30 , 113 , 221 , 67 , 21 , 88 , 43 , 211 , 17 , 190 , 252 , 14 , 186 ,
607
+ 225 , 200 , 135 , 186 , 168 , 255 , 232 , 51 , 151 , 183 , 194 , 134 , 160 , 250 , 191 ,
608
+ 141 ,
609
+ ] ,
610
+ nonce: [ 117 , 86 , 137 , 126 , 205 , 2 , 34 , 50 , 18 , 20 , 165 , 104 ] ,
611
+ orig_size: 34 ,
612
+ } ,
613
+ LargeBlobArrayElement {
614
+ ciphertext: vec![
615
+ 71 , 124 , 111 , 114 , 77 , 240 , 163 , 5 , 124 , 7 , 191 , 2 , 177 , 167 , 200 , 95 , 248 ,
616
+ 163 , 235 , 77 , 195 , 106 , 253 , 23 , 183 , 119 , 55 , 17 , 50 , 238 , 217 , 248 , 56 ,
617
+ 135 , 48 , 49 , 101 , 132 , 66 , 78 , 58 , 23 , 101 , 77 , 52 , 213 , 89 , 73 , 34 , 61 ,
618
+ 237 , 8 , 219 , 1 , 208 , 245 , 129 , 101 , 234 , 114 , 170 , 54 , 7 , 147 , 59 , 226 , 32 ,
619
+ ] ,
620
+ nonce: [ 99 , 132 , 251 , 236 , 134 , 156 , 86 , 195 , 121 , 49 , 205 , 162 ] ,
621
+ orig_size: 36 ,
622
+ } ,
623
+ LargeBlobArrayElement {
624
+ ciphertext: vec![
625
+ 212 , 135 , 116 , 12 , 170 , 245 , 186 , 103 , 147 , 112 , 196 , 29 , 43 , 120 , 236 ,
626
+ 175 , 205 , 84 , 184 , 231 , 118 , 152 , 76 , 60 , 216 , 128 , 204 , 166 , 96 , 8 , 67 , 3 ,
627
+ 163 , 242 , 243 , 124 , 156 , 65 , 138 , 98 , 66 , 46 , 201 , 40 , 219 , 236 , 53 , 43 ,
628
+ 107 , 14 , 135 , 23 , 99 , 150 , 240 , 14 , 234 , 153 , 115 , 94 , 180 , 117 , 162 , 213 ,
629
+ ] ,
630
+ nonce: [ 231 , 165 , 15 , 21 , 64 , 8 , 234 , 133 , 6 , 223 , 226 , 134 ] ,
631
+ orig_size: 34 ,
632
+ } ,
633
+ ] ,
634
+ hash : [
635
+ 0x15 , 0xee , 0x84 , 0xa0 , 0xce , 0x5d , 0xa7 , 0xd6 , 0x6d , 0x3e , 0xb6 , 0xf2 , 0xc1 , 0x40 ,
636
+ 0x28 , 0x65 ,
637
+ ] ,
638
+ byte_len : 463 ,
639
+ }
640
+ }
641
+
642
+ #[ rustfmt:: skip]
643
+ pub const LARGE_BLOB_ARRAY : [ u8 ; 484 ] = [
644
+ 0xa1 , // map(1)
645
+ 0x01 , // unsigned(1)
646
+ 0x59 , 0x01 , 0xdf , // bytes(479)
647
+ 0x83 , // array(3)
648
+ 0xa3 , // map(3)
649
+ 0x01 , // unsigned(1) - ciphertext
650
+ 0x98 , 0x40 , // array(64)
651
+ 0x18 , 0x74 , 0x18 , 0xc7 , 0x18 , 0x52 , 0x18 , 0xce , 0x18 , 0x44 , 0x18 , 0x83 , 0x18 , 0xed , 0x18 , 0xf2 , 0x18 , 0xd5 , 0x18 , 0x90 , 0x18 , 0xf4 , 0x18 , 0xb9 , 0x18 , 0x9b , 0x18 , 0x94 , 0x18 , 0xd9 , 0x18 , 0x3e , 0x18 , 0xf5 , 0x05 , 0x18 , 0x80 , 0x18 , 0xa2 , 0x18 , 0xb0 , 0x18 , 0x63 , 0x05 , 0x18 , 0xa0 , 0x18 , 0xba , 0x18 , 0x44 , 0x18 , 0x58 , 0x18 , 0x8c , 0x18 , 0x26 , 0x18 , 0xff , 0x18 , 0xa8 , 0x18 , 0xfe , 0x18 , 0x58 , 0x18 , 0xa1 , 0x18 , 0xbc , 0x18 , 0x1e , 0x18 , 0x71 , 0x18 , 0xdd , 0x18 , 0x43 , 0x15 , 0x18 , 0x58 , 0x18 , 0x2b , 0x18 , 0xd3 , 0x11 , 0x18 , 0xbe , 0x18 , 0xfc , 0x0e , 0x18 , 0xba , 0x18 , 0xe1 , 0x18 , 0xc8 , 0x18 , 0x87 , 0x18 , 0xba , 0x18 , 0xa8 , 0x18 , 0xff , 0x18 , 0xe8 , 0x18 , 0x33 , 0x18 , 0x97 , 0x18 , 0xb7 , 0x18 , 0xc2 , 0x18 , 0x86 , 0x18 , 0xa0 , 0x18 , 0xfa , 0x18 , 0xbf , 0x18 , 0x8d ,
652
+ 0x02 , // unsigned(2) - nonce
653
+ 0x8c , // array(12)
654
+ 0x18 , 0x75 , 0x18 , 0x56 , 0x18 , 0x89 , 0x18 , 0x7e , 0x18 , 0xcd , 0x02 , 0x18 , 0x22 , 0x18 , 0x32 , 0x12 , 0x14 , 0x18 , 0xa5 , 0x18 , 0x68 ,
655
+ 0x03 , // unsigned(3) - origSize
656
+ 0x18 , 0x22 , // unsigned(34)
657
+ 0xa3 , // map(3)
658
+ 0x01 , // unsigned(1) - ciphertext
659
+ 0x98 , 0x43 , // array(67)
660
+ 0x18 , 0x47 , 0x18 , 0x7c , 0x18 , 0x6f , 0x18 , 0x72 , 0x18 , 0x4d , 0x18 , 0xf0 , 0x18 , 0xa3 , 0x05 , 0x18 , 0x7c , 0x07 , 0x18 , 0xbf , 0x02 , 0x18 , 0xb1 , 0x18 , 0xa7 , 0x18 , 0xc8 , 0x18 , 0x5f , 0x18 , 0xf8 , 0x18 , 0xa3 , 0x18 , 0xeb , 0x18 , 0x4d , 0x18 , 0xc3 , 0x18 , 0x6a , 0x18 , 0xfd , 0x17 , 0x18 , 0xb7 , 0x18 , 0x77 , 0x18 , 0x37 , 0x11 , 0x18 , 0x32 , 0x18 , 0xee , 0x18 , 0xd9 , 0x18 , 0xf8 , 0x18 , 0x38 , 0x18 , 0x87 , 0x18 , 0x30 , 0x18 , 0x31 , 0x18 , 0x65 , 0x18 , 0x84 , 0x18 , 0x42 , 0x18 , 0x4e , 0x18 , 0x3a , 0x17 , 0x18 , 0x65 , 0x18 , 0x4d , 0x18 , 0x34 , 0x18 , 0xd5 , 0x18 , 0x59 , 0x18 , 0x49 , 0x18 , 0x22 , 0x18 , 0x3d , 0x18 , 0xed , 0x08 , 0x18 , 0xdb , 0x01 , 0x18 , 0xd0 , 0x18 , 0xf5 , 0x18 , 0x81 , 0x18 , 0x65 , 0x18 , 0xea , 0x18 , 0x72 , 0x18 , 0xaa , 0x18 , 0x36 , 0x07 , 0x18 , 0x93 , 0x18 , 0x3b , 0x18 , 0xe2 , 0x18 , 0x20 ,
661
+ 0x02 , // unsigned(2)
662
+ 0x8c , // array(12) - nonce
663
+ 0x18 , 0x63 , 0x18 , 0x84 , 0x18 , 0xfb , 0x18 , 0xec , 0x18 , 0x86 , 0x18 , 0x9c , 0x18 , 0x56 , 0x18 , 0xc3 , 0x18 , 0x79 , 0x18 , 0x31 , 0x18 , 0xcd , 0x18 , 0xa2 ,
664
+ 0x03 , // unsigned(3) - origSize
665
+ 0x18 , 0x24 , // unsigned(36)
666
+ 0xa3 , // map(3)
667
+ 0x01 , // unsigned(1) - ciphertext
668
+ 0x98 , 0x40 , // array(64)
669
+ 0x18 , 0xd4 , 0x18 , 0x87 , 0x18 , 0x74 , 0x0c , 0x18 , 0xaa , 0x18 , 0xf5 , 0x18 , 0xba , 0x18 , 0x67 , 0x18 , 0x93 , 0x18 , 0x70 , 0x18 , 0xc4 , 0x18 , 0x1d , 0x18 , 0x2b , 0x18 , 0x78 , 0x18 , 0xec , 0x18 , 0xaf , 0x18 , 0xcd , 0x18 , 0x54 , 0x18 , 0xb8 , 0x18 , 0xe7 , 0x18 , 0x76 , 0x18 , 0x98 , 0x18 , 0x4c , 0x18 , 0x3c , 0x18 , 0xd8 , 0x18 , 0x80 , 0x18 , 0xcc , 0x18 , 0xa6 , 0x18 , 0x60 , 0x08 , 0x18 , 0x43 , 0x03 , 0x18 , 0xa3 , 0x18 , 0xf2 , 0x18 , 0xf3 , 0x18 , 0x7c , 0x18 , 0x9c , 0x18 , 0x41 , 0x18 , 0x8a , 0x18 , 0x62 , 0x18 , 0x42 , 0x18 , 0x2e , 0x18 , 0xc9 , 0x18 , 0x28 , 0x18 , 0xdb , 0x18 , 0xec , 0x18 , 0x35 , 0x18 , 0x2b , 0x18 , 0x6b , 0x0e , 0x18 , 0x87 , 0x17 , 0x18 , 0x63 , 0x18 , 0x96 , 0x18 , 0xf0 , 0x0e , 0x18 , 0xea , 0x18 , 0x99 , 0x18 , 0x73 , 0x18 , 0x5e , 0x18 , 0xb4 , 0x18 , 0x75 , 0x18 , 0xa2 , 0x18 , 0xd5 ,
670
+ 0x02 , // unsigned(2) - nonce
671
+ 0x8c , // array(12)
672
+ 0x18 , 0xe7 , 0x18 , 0xa5 , 0x0f , 0x15 , 0x18 , 0x40 , 0x08 , 0x18 , 0xea , 0x18 , 0x85 , 0x06 , 0x18 , 0xdf , 0x18 , 0xe2 , 0x18 , 0x86 ,
673
+ 0x03 , // unsigned(3) - origSize
674
+ 0x18 , 0x22 , // unsigned(34)
675
+ 0x15 , 0xee , 0x84 , 0xa0 , 0xce , 0x5d , 0xa7 , 0xd6 , 0x6d , 0x3e , 0xb6 , 0xf2 , 0xc1 , 0x40 , 0x28 , 0x65 // trailing hash-bytes
676
+ ] ;
677
+ }
0 commit comments