#!/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

default_slot=2
default_time=int(time.mktime(time.gmtime()))
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" % (secret.encode('hex'))
    response = YK.challenge_response(secret, slot=args.slot)
    # format with appropriate number of leading zeros
    fmt = "%." + str(args.digits) + "i"
    totp_str = fmt % (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, e:
        print "ERROR: %s" % (e.reason)
        return 1

    if not otp:
        return 1

    print otp
    return 0

if __name__ == '__main__':
    sys.exit(main())
