@@ -26,7 +26,7 @@ import (
2626)
2727
2828const (
29- version = "2.1.1 "
29+ version = "2.2.0 "
3030 maxNameLen = 20
3131 period = 30
3232)
@@ -66,7 +66,7 @@ func wipe(bytes []byte) {
6666 runtime .GC ()
6767}
6868
69- func oneTimePassword (secret []byte , size , algorithm string ) string {
69+ func oneTimePassword (secret []byte , size , algorithm string , epoch int64 ) string {
7070 decsecret := make ([]byte , base32 .StdEncoding .WithPadding (base32 .NoPadding ).DecodedLen (len (secret )))
7171 _ , err := base32 .StdEncoding .WithPadding (base32 .NoPadding ).Decode (decsecret , secret )
7272 //wipe(secret)
@@ -75,7 +75,7 @@ func oneTimePassword(secret []byte, size, algorithm string) string {
7575 os .Exit (2 )
7676 }
7777
78- value := toBytes (time .Now ().Unix () / period )
78+ value := toBytes (( time .Now ().Unix () + 30 * epoch ) / period )
7979 var hash []byte
8080 switch algorithm {
8181 case "SHA1" : // Sign the value using HMAC-SHA1
@@ -186,17 +186,18 @@ func addEntry(name string, secret []byte, size, algorithm string, clearscr bool)
186186
187187 fmt .Fprintf (os .Stderr , green + " Entry '" + yellow + name + green + "' %s\n " , action )
188188 if redirected {
189- totp := oneTimePassword (secret , size , algorithm )
189+ totp := oneTimePassword (secret , size , algorithm , 0 )
190190 fmt .Println (totp )
191191 wipe (secret )
192192 return
193193 }
194194
195195 signal .Notify (interrupt , os .Interrupt , syscall .SIGINT )
196196 for {
197- totp := oneTimePassword (secret , size , algorithm )
197+ totp := oneTimePassword (secret , size , algorithm , 0 )
198+ ntotp := oneTimePassword (secret , size , algorithm , 1 )
198199 left := period - time .Now ().Unix ()% period
199- fmt .Fprintf (os .Stderr , blue + "\r TOTP: " + yellow + totp + blue + " Validity:" + yellow +
200+ fmt .Fprintf (os .Stderr , blue + "\r TOTP: " + green + totp + blue + " Next: " + magenta + ntotp + blue + " Validity:" + yellow +
200201 " %2d" + blue + "s " + def + "[Press " + green + "Ctrl-C" + def + " to exit] " , left )
201202 go func () {
202203 <- interrupt
@@ -221,17 +222,18 @@ func showSingleTotp(secret []byte, size, algorithm string) {
221222 secret = checkBase32 (secret )
222223 }
223224 if redirected {
224- totp := oneTimePassword (secret , size , algorithm )
225+ totp := oneTimePassword (secret , size , algorithm , 0 )
225226 wipe (secret )
226227 fmt .Println (totp )
227228 return
228229 }
229230
230231 signal .Notify (interrupt , os .Interrupt , syscall .SIGINT )
231232 for {
232- totp := oneTimePassword (secret , size , algorithm )
233+ totp := oneTimePassword (secret , size , algorithm , 0 )
234+ ntotp := oneTimePassword (secret , size , algorithm , 1 )
233235 left := period - time .Now ().Unix ()% period
234- fmt .Fprintf (os .Stderr , blue + "\r TOTP: " + yellow + totp + blue + " Validity:" + yellow + " %2d" + blue + "s " + def + "[Press " + green + "Ctrl-C" + def + " to exit] " , left )
236+ fmt .Fprintf (os .Stderr , blue + "\r TOTP: " + green + totp + blue + " Next: " + magenta + ntotp + blue + " Validity:" + yellow + " %2d" + blue + "s " + def + "[Press " + green + "Ctrl-C" + def + " to exit] " , left )
235237 go func () {
236238 <- interrupt
237239 fmt .Fprintf (os .Stderr , cls )
@@ -357,13 +359,13 @@ func clipTOTP(name string) {
357359 return
358360 }
359361
360- totp := oneTimePassword (secret , db .Entries [name ].Digits , db .Entries [name ].Algorithm )
362+ totp := oneTimePassword (secret , db .Entries [name ].Digits , db .Entries [name ].Algorithm , 0 )
361363 clipboard .WriteAll (totp )
362364 left := period - time .Now ().Unix ()% period
363365 fmt .Fprintf (os .Stderr , green + "TOTP of " + yellow + "'" + name + "'" + green + " copied to clipboard, valid for" + yellow + " %d " + green + "s\n " , left )
364366}
365367
366- func showTotps (regex string ) {
368+ func showTotps (regex string , next bool ) {
367369 db , err := readDb (false )
368370 exitOnError (err , "Failure opening datafile for showing TOTPs" )
369371
@@ -376,12 +378,13 @@ func showTotps(regex string) {
376378 }
377379 if redirected {
378380 for _ , name := range names {
379- totp := oneTimePassword (db .Entries [name ].Secret , db .Entries [name ].Digits , db .Entries [name ].Algorithm )
381+ totp := oneTimePassword (db .Entries [name ].Secret , db .Entries [name ].Digits , db .Entries [name ].Algorithm , 0 )
382+ ntotp := oneTimePassword (db .Entries [name ].Secret , db .Entries [name ].Digits , db .Entries [name ].Algorithm , 1 )
380383 tag := name
381384 if len (name ) > maxNameLen {
382385 tag = name [:maxNameLen ]
383386 }
384- fmt .Printf ("%v %v \n " , totp , tag )
387+ fmt .Printf ("%v (%v) %v \n " , totp , ntotp , tag )
385388 }
386389 return
387390 }
@@ -398,7 +401,16 @@ func showTotps(regex string) {
398401
399402 // Check display capabilities
400403 w , h , _ := term .GetSize (int (os .Stdout .Fd ()))
401- cols := (w + 1 ) / (8 + 1 + maxNameLen + 1 )
404+ cols , hdr , hdrspc := 0 , "" , ""
405+ if next {
406+ cols = (w + 1 ) / (8 + 1 + 8 + 1 + maxNameLen + 1 )
407+ hdr = " TOTP nextTOTP - Name"
408+ hdrspc = fmt .Sprintf (strings .Repeat (" " , maxNameLen - 6 ))
409+ } else {
410+ cols = (w + 1 ) / (8 + 1 + maxNameLen + 1 )
411+ hdr = " TOTP - Name"
412+ hdrspc = fmt .Sprintf (strings .Repeat (" " , maxNameLen - 4 ))
413+ }
402414 if cols < 1 {
403415 exitOnError (errr , "Terminal too narrow to properly display entries" )
404416 }
@@ -408,23 +420,28 @@ func showTotps(regex string) {
408420 }
409421
410422 sort .Strings (names )
411-
412423 fmtstr := "%s %-" + fmt .Sprint (maxNameLen ) + "s"
413424 for {
414- fmt .Fprintf (os .Stderr , cls + blue + " TOTP - Name" )
425+ fmt .Fprintf (os .Stderr , cls + blue + hdr )
415426 for i := 1 ; i < cols && i < nn ; i ++ {
416- fmt .Fprintf (os .Stderr , strings . Repeat ( " " , maxNameLen - 1 ) + "TOTP - Name" )
427+ fmt .Fprintf (os .Stderr , hdrspc + hdr )
417428 }
418429 fmt .Fprintln (os .Stderr )
419430 n := 0
420431 for _ , name := range names {
421- totp := oneTimePassword (db .Entries [name ].Secret , db .Entries [name ].Digits , db .Entries [name ].Algorithm )
432+ totp := oneTimePassword (db .Entries [name ].Secret , db .Entries [name ].Digits , db .Entries [name ].Algorithm , 0 )
422433 totp = fmt .Sprintf ("%8v" , totp )
423434 tag := name
424435 if len (name ) > maxNameLen {
425436 tag = name [:maxNameLen ]
426437 }
427- fmt .Fprintf (os .Stderr , fmtstr , green + totp + def , tag )
438+ if next {
439+ ntotp := oneTimePassword (db .Entries [name ].Secret , db .Entries [name ].Digits , db .Entries [name ].Algorithm , 1 )
440+ ntotp = fmt .Sprintf ("%8v" , ntotp )
441+ fmt .Fprintf (os .Stderr , fmtstr , green + totp + magenta + ntotp + def , tag )
442+ } else {
443+ fmt .Fprintf (os .Stderr , fmtstr , green + totp + def , tag )
444+ }
428445 n += 1
429446 if n % cols == 0 {
430447 fmt .Fprintln (os .Stderr )
@@ -638,7 +655,7 @@ func importEntries(filename string) {
638655func main () {
639656 self , cmd , regex , datafile , name , nname , file := "" , "" , "" , "" , "" , "" , ""
640657 var secret []byte
641- datafileflag , sizeflag , algorithmflag , size , algorithm , ddash , cas := 0 , 0 , 0 , "6" , "SHA1" , false , false
658+ datafileflag , sizeflag , algorithmflag , size , algorithm , ddash , cas , next := 0 , 0 , 0 , "6" , "SHA1" , false , false , false
642659 o , _ := os .Stdout .Stat ()
643660 if (o .Mode () & os .ModeCharDevice ) == os .ModeCharDevice {
644661 redirected = false
@@ -677,6 +694,10 @@ func main() {
677694 force = true
678695 continue
679696 }
697+ if arg == "-n" || arg == "--next" {
698+ next = true
699+ continue
700+ }
680701 if arg == "-d" || arg == "--datafile" {
681702 if datafileflag > 0 {
682703 usage ("datafile already specified with -d/--datafile" )
@@ -708,7 +729,7 @@ func main() {
708729 return
709730
710731 case "show" , "view" :
711- cmd = "s" // [REGEX] [-c/--case]
732+ cmd = "s" // [REGEX] [-c/--case] [-n/--next]
712733 case "list" , "ls" :
713734 cmd = "l" // [REGEX] [-c/--case]
714735 case "rename" , "move" , "mv" :
@@ -820,7 +841,10 @@ func main() {
820841 }
821842 }
822843 // All arguments have been parsed, check
823- if cas && cmd != "s" && cmd != "l" {
844+ if next && cmd != "" && cmd != "s" {
845+ usage ("flag -n/--next can only be given on show/view command" )
846+ }
847+ if cas && cmd != "" && cmd != "s" && cmd != "l" {
824848 usage ("flag -c/--case can only be given on show/view and list/ls commands" )
825849 }
826850 if datafileflag == 1 {
@@ -840,7 +864,7 @@ func main() {
840864 }
841865 switch cmd {
842866 case "" , "s" :
843- showTotps (regex )
867+ showTotps (regex , next )
844868 case "l" :
845869 showNames (regex )
846870 case "a" :
@@ -895,8 +919,8 @@ func usage(err string) {
895919 blue + "Datafile" + def + ": " + magenta + dbPath + def + " (default, depends on the binary's name)\n * " +
896920 blue + "Usage" + def + ": " + magenta + self + def + " [" + green + "COMMAND" + def + "] [ " + yellow + "-d" + def + " | " + yellow + "--datafile " + cyan + " DATAFILE" + def + " ]\n " +
897921 " == " + green + "COMMAND" + def + ":\n " +
898- "[ " + green + "show" + def + " | " + green + "view" + def + " ] [" + blue + "REGEX" + def + " [ " + yellow + "-c" + def + " | " + yellow + "--case" + def + " ]]\n " +
899- " Display all TOTPs with " + blue + "NAME" + def + "s [matching " + blue + "REGEX" + def + "] (" + green + "show " + def + "/" + green + "view " + def + " is optional ).\n " +
922+ "[ " + green + "show" + def + " | " + green + "view" + def + " ] [" + blue + "REGEX" + def + " [ " + yellow + "-c" + def + " | " + yellow + "--case" + def + " ]] [ " + yellow + "-n" + def + " | " + yellow + "--next" + def + " ] \n " +
923+ " Display all TOTPs with " + blue + "NAME" + def + "s [matching " + blue + "REGEX" + def + "] (" + yellow + "-n " + def + "/" + yellow + "--next " + def + ": show next TOTP ).\n " +
900924 green + "list" + def + " | " + green + "ls" + def + " [" + blue + "REGEX" + def + " [ " + yellow + "-c" + def + " | " + yellow + "--case" + def + " ]]\n " +
901925 " List all " + blue + "NAME" + def + "s [matching " + blue + "REGEX" + def + "].\n " +
902926 green + "add" + def + " | " + green + "insert" + def + " | " + green + "entry " + blue + "NAME" + def + " [" + yellow + "TOTP-OPTIONS" + def + "] [ " + yellow + "-f" + def + " | " + yellow + "--force" + def + " ] [" + blue + "SECRET" + def + "]\n " +
0 commit comments