1
1
use crate :: error:: * ;
2
- use crate :: luks;
3
2
use crate :: * ;
4
3
5
4
use structopt:: StructOpt ;
@@ -12,8 +11,10 @@ use std::io::Write;
12
11
use std:: process:: exit;
13
12
use std:: thread;
14
13
14
+ use crate :: luks:: { Fido2LuksToken , LuksDevice } ;
15
15
use crate :: util:: sha256;
16
16
use std:: borrow:: Cow ;
17
+ use std:: collections:: HashSet ;
17
18
use std:: time:: SystemTime ;
18
19
19
20
#[ derive( Debug , Eq , PartialEq , Clone ) ]
@@ -25,6 +26,12 @@ impl Display for HexEncoded {
25
26
}
26
27
}
27
28
29
+ impl AsRef < [ u8 ] > for HexEncoded {
30
+ fn as_ref ( & self ) -> & [ u8 ] {
31
+ & self . 0 [ ..]
32
+ }
33
+ }
34
+
28
35
impl FromStr for HexEncoded {
29
36
type Err = hex:: FromHexError ;
30
37
@@ -59,7 +66,7 @@ impl<T: Display + FromStr> FromStr for CommaSeparated<T> {
59
66
60
67
#[ derive( Debug , StructOpt ) ]
61
68
pub struct Credentials {
62
- /// FIDO credential ids, seperated by ',' generate using fido2luks credential
69
+ /// FIDO credential ids, separated by ',' generate using fido2luks credential
63
70
#[ structopt( name = "credential-id" , env = "FIDO2LUKS_CREDENTIAL_ID" ) ]
64
71
pub ids : CommaSeparated < HexEncoded > ,
65
72
}
@@ -282,6 +289,45 @@ pub enum Command {
282
289
/// Check if an authenticator is connected
283
290
#[ structopt( name = "connected" ) ]
284
291
Connected ,
292
+ Token ( TokenCommand ) ,
293
+ }
294
+
295
+ ///LUKS2 token related operations
296
+ #[ derive( Debug , StructOpt ) ]
297
+ pub enum TokenCommand {
298
+ /// List all tokens associated with the specified device
299
+ List {
300
+ #[ structopt( env = "FIDO2LUKS_DEVICE" ) ]
301
+ device : PathBuf ,
302
+ /// Dump all credentials as CSV
303
+ #[ structopt( long = "csv" ) ]
304
+ csv : bool ,
305
+ } ,
306
+ /// Add credential to a keyslot
307
+ Add {
308
+ #[ structopt( env = "FIDO2LUKS_DEVICE" ) ]
309
+ device : PathBuf ,
310
+ #[ structopt( flatten) ]
311
+ credentials : Credentials ,
312
+ /// Slot to which the credentials will be added
313
+ #[ structopt( long = "slot" , env = "FIDO2LUKS_DEVICE_SLOT" ) ]
314
+ slot : u32 ,
315
+ } ,
316
+ /// Remove credentials from token(s)
317
+ Remove {
318
+ #[ structopt( env = "FIDO2LUKS_DEVICE" ) ]
319
+ device : PathBuf ,
320
+ #[ structopt( flatten) ]
321
+ credentials : Credentials ,
322
+ /// Token from which the credentials will be removed
323
+ #[ structopt( long = "token" ) ]
324
+ token_id : Option < u32 > ,
325
+ } ,
326
+ /// Remove all unassigned tokens
327
+ GC {
328
+ #[ structopt( env = "FIDO2LUKS_DEVICE" ) ]
329
+ device : PathBuf ,
330
+ } ,
285
331
}
286
332
287
333
pub fn parse_cmdline ( ) -> Args {
@@ -371,7 +417,9 @@ pub fn run_cli() -> Fido2LuksResult<()> {
371
417
secret. salt . obtain ( & secret. password_helper )
372
418
}
373
419
} ;
374
- let other_secret = |salt_q : & str , verify : bool | -> Fido2LuksResult < ( Vec < u8 > , Option < FidoCredential > ) > {
420
+ let other_secret = |salt_q : & str ,
421
+ verify : bool |
422
+ -> Fido2LuksResult < ( Vec < u8 > , Option < FidoCredential > ) > {
375
423
match other_secret {
376
424
OtherSecret {
377
425
keyfile : Some ( file) ,
@@ -386,7 +434,10 @@ pub fn run_cli() -> Fido2LuksResult<()> {
386
434
pin. as_deref ( ) ,
387
435
)
388
436
. map ( |( secret, cred) | ( secret[ ..] . to_vec ( ) , Some ( cred) ) ) ?) ,
389
- _ => Ok ( ( util:: read_password ( salt_q, verify) ?. as_bytes ( ) . to_vec ( ) , None ) ) ,
437
+ _ => Ok ( (
438
+ util:: read_password ( salt_q, verify) ?. as_bytes ( ) . to_vec ( ) ,
439
+ None ,
440
+ ) ) ,
390
441
}
391
442
} ;
392
443
let secret = |verify : bool | -> Fido2LuksResult < ( [ u8 ; 32 ] , FidoCredential ) > {
@@ -397,22 +448,20 @@ pub fn run_cli() -> Fido2LuksResult<()> {
397
448
pin. as_deref ( ) ,
398
449
)
399
450
} ;
451
+ let mut luks_dev = LuksDevice :: load ( & luks. device ) ?;
400
452
// Non overlap
401
453
match & args. command {
402
- Command :: AddKey {
403
- exclusive, ..
404
- } => {
454
+ Command :: AddKey { exclusive, .. } => {
405
455
let ( existing_secret, _) = other_secret ( "Current password" , false ) ?;
406
456
let ( new_secret, cred) = secret ( true ) ?;
407
- let added_slot = luks:: add_key (
408
- & luks. device ,
457
+ let added_slot = luks_dev. add_key (
409
458
& new_secret,
410
459
& existing_secret[ ..] ,
411
460
luks_mod. kdf_time . or ( Some ( 10 ) ) ,
412
461
Some ( & cred. id [ ..] ) . filter ( |_| * token) ,
413
462
) ?;
414
463
if * exclusive {
415
- let destroyed = luks :: remove_keyslots ( & luks . device , & [ added_slot] ) ?;
464
+ let destroyed = luks_dev . remove_keyslots ( & [ added_slot] ) ?;
416
465
println ! (
417
466
"Added to key to device {}, slot: {}\n Removed {} old keys" ,
418
467
luks. device. display( ) ,
@@ -432,16 +481,14 @@ pub fn run_cli() -> Fido2LuksResult<()> {
432
481
let ( existing_secret, _) = secret ( false ) ?;
433
482
let ( replacement_secret, cred) = other_secret ( "Replacement password" , true ) ?;
434
483
let slot = if * add_password {
435
- luks:: add_key (
436
- & luks. device ,
484
+ luks_dev. add_key (
437
485
& replacement_secret[ ..] ,
438
486
& existing_secret,
439
487
luks_mod. kdf_time ,
440
488
cred. as_ref ( ) . filter ( |_| * token) . map ( |cred| & cred. id [ ..] ) ,
441
489
)
442
490
} else {
443
- luks:: replace_key (
444
- & luks. device ,
491
+ luks_dev. replace_key (
445
492
& replacement_secret[ ..] ,
446
493
& existing_secret,
447
494
luks_mod. kdf_time ,
@@ -499,14 +546,12 @@ pub fn run_cli() -> Fido2LuksResult<()> {
499
546
} ;
500
547
501
548
let mut retries = * retries;
549
+ let mut luks_dev = LuksDevice :: load ( & luks. device ) ?;
502
550
loop {
503
551
let secret = match & args. command {
504
552
Command :: Open { credentials, .. } => secret ( Cow :: Borrowed ( & credentials. ids . 0 ) )
505
- . and_then ( |( secret, _cred) | {
506
- luks:: open_container ( & luks. device , & name, & secret, luks. slot )
507
- } ) ,
508
- Command :: OpenToken { .. } => luks:: open_container_token (
509
- & luks. device ,
553
+ . and_then ( |( secret, _cred) | luks_dev. activate ( & name, & secret, luks. slot ) ) ,
554
+ Command :: OpenToken { .. } => luks_dev. activate_token (
510
555
& name,
511
556
Box :: new ( |credentials : Vec < String > | {
512
557
let creds = credentials
@@ -516,6 +561,7 @@ pub fn run_cli() -> Fido2LuksResult<()> {
516
561
secret ( Cow :: Owned ( creds) )
517
562
. map ( |( secret, cred) | ( secret, hex:: encode ( & cred. id ) ) )
518
563
} ) ,
564
+ luks. slot ,
519
565
) ,
520
566
_ => unreachable ! ( ) ,
521
567
} ;
@@ -531,7 +577,7 @@ pub fn run_cli() -> Fido2LuksResult<()> {
531
577
retries -= 1 ;
532
578
eprintln ! ( "{}" , e) ;
533
579
}
534
- res => break res,
580
+ res => break res. map ( |_| ( ) ) ,
535
581
}
536
582
}
537
583
}
@@ -542,5 +588,129 @@ pub fn run_cli() -> Fido2LuksResult<()> {
542
588
}
543
589
_ => exit ( 1 ) ,
544
590
} ,
591
+ Command :: Token ( cmd) => match cmd {
592
+ TokenCommand :: List {
593
+ device,
594
+ csv : dump_credentials,
595
+ } => {
596
+ let mut dev = LuksDevice :: load ( device) ?;
597
+ let mut creds = Vec :: new ( ) ;
598
+ for token in dev. tokens ( ) ? {
599
+ let ( id, token) = token?;
600
+ for cred in token. credential . iter ( ) {
601
+ if !creds. contains ( cred) {
602
+ creds. push ( cred. clone ( ) ) ;
603
+ if * dump_credentials {
604
+ print ! ( "{}{}" , if creds. len( ) == 1 { "" } else { "," } , cred) ;
605
+ }
606
+ }
607
+ }
608
+ if * dump_credentials {
609
+ continue ;
610
+ }
611
+ println ! (
612
+ "{}:\n \t Slots: {}\n \t Credentials: {}" ,
613
+ id,
614
+ if token. keyslots. is_empty( ) {
615
+ "None" . into( )
616
+ } else {
617
+ token. keyslots. iter( ) . cloned( ) . collect:: <Vec <_>>( ) . join( "," )
618
+ } ,
619
+ token
620
+ . credential
621
+ . iter( )
622
+ . map( |cred| format!(
623
+ "{} ({})" ,
624
+ cred,
625
+ creds. iter( ) . position( |c| c == cred) . unwrap( ) . to_string( )
626
+ ) )
627
+ . collect:: <Vec <_>>( )
628
+ . join( "," )
629
+ ) ;
630
+ }
631
+ if * dump_credentials {
632
+ println ! ( ) ;
633
+ }
634
+ Ok ( ( ) )
635
+ }
636
+ TokenCommand :: Add {
637
+ device,
638
+ credentials,
639
+ slot,
640
+ } => {
641
+ let mut dev = LuksDevice :: load ( device) ?;
642
+ let mut tokens = Vec :: new ( ) ;
643
+ for token in dev. tokens ( ) ? {
644
+ let ( id, token) = token?;
645
+ if token. keyslots . contains ( & slot. to_string ( ) ) {
646
+ tokens. push ( ( id, token) ) ;
647
+ }
648
+ }
649
+ let count = if tokens. is_empty ( ) {
650
+ dev. add_token ( & Fido2LuksToken :: with_credentials ( & credentials. ids . 0 , * slot) ) ?;
651
+ 1
652
+ } else {
653
+ tokens. len ( )
654
+ } ;
655
+ for ( id, mut token) in tokens {
656
+ token
657
+ . credential
658
+ . extend ( credentials. ids . 0 . iter ( ) . map ( |h| h. to_string ( ) ) ) ;
659
+ dev. update_token ( id, & token) ?;
660
+ }
661
+ println ! ( "Updated {} tokens" , count) ;
662
+ Ok ( ( ) )
663
+ }
664
+ TokenCommand :: Remove {
665
+ device,
666
+ credentials,
667
+ token_id,
668
+ } => {
669
+ let mut dev = LuksDevice :: load ( device) ?;
670
+ let mut tokens = Vec :: new ( ) ;
671
+ for token in dev. tokens ( ) ? {
672
+ let ( id, token) = token?;
673
+ if let Some ( token_id) = token_id {
674
+ if id == * token_id {
675
+ tokens. push ( ( id, token) ) ;
676
+ }
677
+ } else {
678
+ tokens. push ( ( id, token) ) ;
679
+ }
680
+ }
681
+ let count = tokens. len ( ) ;
682
+ for ( id, mut token) in tokens {
683
+ token. credential = token
684
+ . credential
685
+ . into_iter ( )
686
+ . filter ( |cred| !credentials. ids . 0 . iter ( ) . any ( |h| & h. to_string ( ) == cred) )
687
+ . collect ( ) ;
688
+ dev. update_token ( id, & token) ?;
689
+ }
690
+ println ! ( "Updated {} tokens" , count) ;
691
+ Ok ( ( ) )
692
+ }
693
+ TokenCommand :: GC { device } => {
694
+ let mut dev = LuksDevice :: load ( device) ?;
695
+ let mut creds: HashSet < String > = HashSet :: new ( ) ;
696
+ let mut remove = Vec :: new ( ) ;
697
+ for token in dev. tokens ( ) ? {
698
+ let ( id, token) = token?;
699
+ if token. keyslots . is_empty ( ) || token. credential . is_empty ( ) {
700
+ creds. extend ( token. credential ) ;
701
+ remove. push ( id) ;
702
+ }
703
+ }
704
+ for id in remove. iter ( ) . rev ( ) {
705
+ dev. remove_token ( * id) ?;
706
+ }
707
+ println ! (
708
+ "Removed {} tokens, affected credentials: {}" ,
709
+ remove. len( ) ,
710
+ creds. into_iter( ) . collect:: <Vec <_>>( ) . join( "," )
711
+ ) ;
712
+ Ok ( ( ) )
713
+ }
714
+ } ,
545
715
}
546
716
}
0 commit comments