mirror of
https://github.com/gryf/softtoken.git
synced 2025-12-20 21:08:05 +01:00
Previously, when a user wanted to generate an sha1 token, sha256 was used instead. This patch fixes the bug even though the use of sha1 is discouraged.
167 lines
4.9 KiB
Python
167 lines
4.9 KiB
Python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import base64
|
|
import configparser
|
|
import hashlib
|
|
import sys
|
|
from os import path
|
|
from os import urandom
|
|
|
|
from pykeyboard import PyKeyboard
|
|
|
|
import pyotp
|
|
|
|
import pyperclip
|
|
|
|
__version__ = '0.0.2'
|
|
|
|
CONFIG_FILE = '.softtoken.conf'
|
|
|
|
|
|
def load_config():
|
|
dir_path = path.join(path.expanduser('~'), CONFIG_FILE)
|
|
cfg = configparser.SafeConfigParser()
|
|
cfg.read(dir_path)
|
|
return cfg
|
|
|
|
|
|
def save_config(cfg):
|
|
dir_path = path.join(path.expanduser('~'), CONFIG_FILE)
|
|
try:
|
|
with open(dir_path, 'w+') as configfile:
|
|
cfg.write(configfile)
|
|
except Exception:
|
|
print('ERROR: Cannot write config file')
|
|
sys.exit(2)
|
|
|
|
|
|
def create_token(name, hash_function='sha256', digits=6, seed_length=20):
|
|
cfg = load_config()
|
|
if cfg.has_section(name):
|
|
print('Token %s already exists. Delete it first' % name)
|
|
sys.exit(2)
|
|
|
|
seed = urandom(seed_length)
|
|
|
|
cfg.add_section(name)
|
|
cfg.set(name, 'hash', hash_function)
|
|
cfg.set(name, 'digits', str(digits))
|
|
cfg.set(name, 'seed', base64.b32encode(seed))
|
|
|
|
save_config(cfg)
|
|
|
|
print('\nNew Token created:\n\n%s\n-------------' % name)
|
|
print('Seed (hex): %s' % seed.encode('hex'))
|
|
print('Seed (b32): %s\n' % base64.b32encode(seed))
|
|
|
|
|
|
def delete_token(name):
|
|
cfg = load_config()
|
|
if not cfg.has_section(name):
|
|
print('Token %s does not exist' % name)
|
|
sys.exit(2)
|
|
cfg.remove_section(name)
|
|
save_config(cfg)
|
|
print("Token %s successfully deleted" % name)
|
|
|
|
|
|
def print_tokens():
|
|
cfg = load_config()
|
|
for section in cfg.sections():
|
|
print("[*] %s" % section)
|
|
|
|
|
|
def main():
|
|
|
|
parser = argparse.ArgumentParser(version=__version__)
|
|
parser.add_argument('--new', action='store_true', default=False,
|
|
dest='new_token', help='Generate a new Soft Token')
|
|
parser.add_argument('--delete', action='store_true', default=False,
|
|
dest='delete_token', help='Delete a Soft Token')
|
|
parser.add_argument('--list', action='store_true', default=False,
|
|
dest='list_tokens', help='List configured tokens')
|
|
parser.add_argument('--token', '-t', required=False, dest='token_name',
|
|
help='Soft Token name')
|
|
parser.add_argument('--hash', default='sha256', dest='hash_function',
|
|
choices=('sha1', 'sha256', 'sha512'), help='Hash '
|
|
'function to use (default is sha256)')
|
|
parser.add_argument('--digits', '-d', type=int, default=6, dest='digits',
|
|
help='OTP Length (default is 6)')
|
|
parser.add_argument('--length', '-l', type=int, default=20,
|
|
dest='seed_length', help='Seed length in bytes '
|
|
'(default is 20)')
|
|
parser.add_argument('-X', action='store_true', default=False,
|
|
dest='print_focus', help='Output the OTP where '
|
|
'the current focus is')
|
|
parser.add_argument('-C', action='store_true', default=False,
|
|
dest='copy_clipboard', help='Copy OTP to clipboard')
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.list_tokens:
|
|
print_tokens()
|
|
sys.exit(0)
|
|
|
|
if args.token_name is None:
|
|
print("A Token name is required for this action")
|
|
parser.print_help()
|
|
sys.exit(-1)
|
|
|
|
if args.new_token:
|
|
create_token(args.token_name,
|
|
args.hash_function,
|
|
args.digits,
|
|
args.seed_length)
|
|
sys.exit(0)
|
|
|
|
if args.delete_token:
|
|
delete_token(args.token_name)
|
|
sys.exit(0)
|
|
|
|
if args.list_tokens:
|
|
print_tokens()
|
|
sys.exit(0)
|
|
|
|
# Generate new OTP if the token exists
|
|
cfg = load_config()
|
|
if not cfg.has_section(args.token_name):
|
|
print('Token %s does not exist' % args.token_name)
|
|
sys.exit(2)
|
|
|
|
if args.hash_function == 'sha1':
|
|
hf = hashlib.sha1
|
|
elif args.hash_function == 'sha256':
|
|
hf = hashlib.sha256
|
|
elif args.hash_function == 'sha512':
|
|
hf = hashlib.sha512
|
|
|
|
seed = cfg.get(args.token_name, 'seed')
|
|
totp = pyotp.TOTP(seed, digest=hf, digits=args.digits)
|
|
|
|
otp = totp.now()
|
|
|
|
if args.print_focus:
|
|
k = PyKeyboard()
|
|
k.type_string(otp)
|
|
elif args.copy_clipboard:
|
|
pyperclip.copy(otp)
|
|
else:
|
|
print(otp)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|