133 lines
3.8 KiB
Python
Executable File
133 lines
3.8 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
# Copyright (c) 2011, Yubico AB
|
|
# See the file COPYING for licence statement.
|
|
#
|
|
|
|
"""
|
|
This program lets you use the HMAC-SHA-1 in your YubiKey to produce
|
|
OATH TOTP (RFC 6238) codes.
|
|
|
|
To verify the output of this program, first program a YubiKey with the
|
|
RFC 6238 test key "12345678901234567890" (ASCII) :
|
|
|
|
$ ykpersonalize -2 -ochal-resp -ochal-hmac -ohmac-lt64 \
|
|
-o serial-api-visible \
|
|
-a 3132333435363738393031323334353637383930
|
|
|
|
and then examine the OATH codes for the test values (Time) in Appendix B
|
|
of RFC 6238 (SHA1) :
|
|
|
|
Time SHA1
|
|
59 -> 94287082
|
|
1111111109 -> 07081804
|
|
1234567890 -> 89005924
|
|
20000000000 -> 65353130
|
|
|
|
Like this :
|
|
|
|
$ yubikey-totp --step 30 --digits 8 --time 59
|
|
94287082
|
|
$
|
|
|
|
"""
|
|
|
|
|
|
import sys
|
|
import time
|
|
import struct
|
|
import yubico
|
|
import argparse
|
|
import binascii
|
|
|
|
default_slot=2
|
|
default_time=int(time.time())
|
|
default_step=30
|
|
default_digits=6
|
|
|
|
def parse_args():
|
|
"""
|
|
Parse the command line arguments
|
|
"""
|
|
parser = argparse.ArgumentParser(description = "Generate OATH TOTP codes using a YubiKey",
|
|
add_help = True,
|
|
formatter_class = argparse.ArgumentDefaultsHelpFormatter,
|
|
)
|
|
parser.add_argument('-v', '--verbose',
|
|
dest='verbose',
|
|
action='store_true', default=False,
|
|
help='Enable verbose operation'
|
|
)
|
|
parser.add_argument('--debug',
|
|
dest='debug',
|
|
action='store_true', default=False,
|
|
help='Enable debug operation'
|
|
)
|
|
parser.add_argument('--time',
|
|
dest='time',
|
|
type=int, default=default_time,
|
|
required=False,
|
|
help='Time to use as number of seconds since epoch',
|
|
)
|
|
parser.add_argument('--step',
|
|
dest='step',
|
|
type=int, default=default_step,
|
|
required=False,
|
|
help='Time step in use (in seconds)',
|
|
)
|
|
parser.add_argument('--digits',
|
|
dest='digits',
|
|
type=int, default=default_digits,
|
|
required=False,
|
|
help='Length of OTP in decimal digits',
|
|
)
|
|
parser.add_argument('--slot',
|
|
dest='slot',
|
|
type=int, default=default_slot,
|
|
required=False,
|
|
help='YubiKey slot configured for Challenge-Response',
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
return args
|
|
|
|
def make_totp(args):
|
|
"""
|
|
Create an OATH TOTP OTP and return it as a string (to disambiguate leading zeros).
|
|
"""
|
|
YK = yubico.find_yubikey(debug=args.debug)
|
|
if args.debug or args.verbose:
|
|
print("Version : %s " % YK.version())
|
|
if args.debug:
|
|
print("Serial : %i" % YK.serial())
|
|
print("")
|
|
# Do challenge-response
|
|
secret = struct.pack("> Q", args.time / args.step).ljust(64, chr(0x0))
|
|
if args.debug:
|
|
print("Sending challenge : %s\n" % (binascii.hexlify(secret)))
|
|
response = YK.challenge_response(secret, slot=args.slot)
|
|
# format with appropriate number of leading zeros
|
|
totp_str = '%.*i' % (args.digits, yubico.yubico_util.hotp_truncate(response, length=args.digits))
|
|
return totp_str
|
|
|
|
def main():
|
|
""" Main program. """
|
|
args = parse_args()
|
|
|
|
otp = None
|
|
try:
|
|
otp = make_totp(args)
|
|
except yubico.yubico_exception.YubicoError as e:
|
|
print("ERROR: %s" % (e.reason))
|
|
return 1
|
|
|
|
if not otp:
|
|
return 1
|
|
|
|
print(otp)
|
|
return 0
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|