Server IP : 85.214.239.14 / Your IP : 13.59.68.161 Web Server : Apache/2.4.62 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Tue Jan 9 19:45:01 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 7.4.18 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : OFF | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /srv/modoboa/env/lib64/python3.5/site-packages/oath/ |
Upload File : |
# -*- Mode: Python; python-indent-offset: 4 -*- ''' Google Authenticator API ------------------------ Google Authenticator is based on HOTP and TOTP. It provides a simple way of provisionning an OTP generator through a new URL scheme. This module provides parsing and high-level API over the classic HOTP and TOTP APIs provided by the oath.hotp and oath.totp modules. ''' try: from urlparse import urlparse, parse_qs from urllib import urlencode, quote except ImportError: from urllib.parse import urlparse, parse_qs, urlencode, quote import base64 import hashlib from oath import _hotp as hotp from oath import _totp as totp from . import _utils import binascii __all__ = ('GoogleAuthenticator', 'from_b32key', 'GoogleAuthenticatorURI') LABEL = 'label' TYPE = 'type' ALGORITHM = 'algorithm' DIGITS = 'digits' SECRET = 'secret' COUNTER = 'counter' PERIOD = 'period' TOTP = 'totp' HOTP = 'hotp' DRIFT = 'drift' ISSUER = 'issuer' def lenient_b32decode(data): data = data.upper() # Ensure correct case data += ('=' * ((8 - len(data)) % 8)) # Ensure correct padding return base64.b32decode(data.encode('ascii')) def parse_otpauth(otpauth_uri): if not otpauth_uri.startswith('otpauth://'): raise ValueError('Invalid otpauth URI', otpauth_uri) # urlparse in python 2.6 can't handle the otpauth:// scheme, skip it parsed_uri = urlparse(otpauth_uri[8:]) params = dict(((k, v[0]) for k, v in parse_qs(parsed_uri.query).items())) params[LABEL] = parsed_uri.path[1:] params[TYPE] = parsed_uri.hostname if SECRET not in params: raise ValueError('Missing secret field in otpauth URI', otpauth_uri) try: params[SECRET] = _utils.tohex(lenient_b32decode(params[SECRET])) except TypeError: raise ValueError('Invalid base32 encoding of the secret field in ' 'otpauth URI', otpauth_uri) if ALGORITHM in params: params[ALGORITHM] = params[ALGORITHM].lower() if params[ALGORITHM] not in ('sha1', 'sha256', 'sha512', 'md5'): raise ValueError('Invalid value for algorithm field in otpauth ' 'URI', otpauth_uri) else: params[ALGORITHM] = 'sha1' try: params[ALGORITHM] = getattr(hashlib, params[ALGORITHM]) except AttributeError: raise ValueError('Unsupported algorithm %s in othauth URI' % params[ALGORITHM], otpauth_uri) for key in (DIGITS, PERIOD, COUNTER): try: if key in params: params[key] = int(params[key]) except ValueError: raise ValueError('Invalid value for field %s in otpauth URI, must ' 'be a number' % key, otpauth_uri) if COUNTER not in params: params[COUNTER] = 0 # what else ? if DIGITS in params: if params[DIGITS] not in (6, 8): raise ValueError('Invalid value for field digits in othauth URI, it ' 'must 6 or 8', otpauth_uri) else: params[DIGITS] = 6 if params[TYPE] == HOTP and COUNTER not in params: raise ValueError('Missing field counter in otpauth URI, it is ' 'mandatory with the hotp type', otpauth_uri) if params[TYPE] == TOTP and PERIOD not in params: params[PERIOD] = 30 return params def from_b32key(b32_key, state=None): '''Some phone app directly accept a partial b32 encoding, we try to emulate that''' try: lenient_b32decode(b32_key) except TypeError: raise ValueError('invalid base32 value') return GoogleAuthenticator('otpauth://totp/xxx?%s' % urlencode({'secret': b32_key}), state=state) class GoogleAuthenticator(object): def __init__(self, otpauth_uri, state=None): self.otpauth_uri = otpauth_uri self.parsed_otpauth_uri = parse_otpauth(otpauth_uri) self.generator_state = state or {} self.acceptor_state = state or {} @property def label(self): return self.parsed_otpauth_uri[LABEL] def generate(self, t=None): format = 'dec%s' % self.parsed_otpauth_uri[DIGITS] hash = self.parsed_otpauth_uri[ALGORITHM] secret = self.parsed_otpauth_uri[SECRET] state = self.generator_state if self.parsed_otpauth_uri[TYPE] == HOTP: if COUNTER not in state: state[COUNTER] = self.parsed_otpauth_uri[COUNTER] otp = hotp.hotp(secret, state[COUNTER], format=format, hash=hash) state[COUNTER] += 1 return otp elif self.parsed_otpauth_uri[TYPE] == TOTP: period = self.parsed_otpauth_uri[PERIOD] return totp.totp(secret, format=format, period=period, hash=hash, t=t) else: raise NotImplementedError(self.parsed_otpauth_uri[TYPE]) def accept(self, otp, hotp_drift=3, hotp_backward_drift=0, totp_forward_drift=1, totp_backward_drift=1, t=None): format = 'dec%s' % self.parsed_otpauth_uri[DIGITS] hash = self.parsed_otpauth_uri[ALGORITHM] secret = self.parsed_otpauth_uri[SECRET] state = self.acceptor_state if self.parsed_otpauth_uri[TYPE] == HOTP: if COUNTER not in state: state[COUNTER] = self.parsed_otpauth_uri[COUNTER] ok, state[COUNTER] = hotp.accept_hotp(otp, secret, state[COUNTER], format=format, hash=hash, drift=hotp_drift, backward_drift=hotp_backward_drift) return ok elif self.parsed_otpauth_uri[TYPE] == TOTP: period = self.parsed_otpauth_uri[PERIOD] if DRIFT not in state: state[DRIFT] = 0 ok, state[DRIFT] = totp.accept_totp(secret, otp, format=format, period=period, forward_drift=totp_forward_drift, backward_drift=totp_backward_drift, drift=state[DRIFT], t=t) return ok else: raise NotImplementedError(self.parsed_otpauth_uri[TYPE]) class GoogleAuthenticatorURI(object): """ used to create an URI for Google Authenticator, needs to be converted in QR afterwards """ def __init__(self): """ constructor """ return def generate(self, secret, type='totp', account='alex', issuer=None, algo='sha1', digits=6, init_counter=None): """ https://github.com/google/google-authenticator/wiki/Key-Uri-Format """ args = {} uri = 'otpauth://{0}/{1}?{2}' try: # converts the secret to a 16 cars string a = binascii.unhexlify(secret) args[SECRET] = base64.b32encode(a).decode('ascii') except binascii.Error as ex: raise ValueError(str(ex)) except Exception as ex: print(ex) raise ValueError('invalid secret format') if type not in [TOTP, HOTP]: raise ValueError('type should be totp or hotp, got ', type) if type != TOTP: args['type'] = type if algo not in ['sha1', 'sha256', 'sha512']: raise ValueError('algo should be sha1, sha256 or sha512, got ', algo) if algo != 'sha1': args['algorithm'] = algo if init_counter is not None: if type != HOTP: raise ValueError('type should be hotp when ', 'setting init_counter') if int(init_counter) < 0: raise ValueError('init_counter should be positive') args[COUNTER] = int(init_counter) digits = int(digits) if digits != 6 and digits != 8: raise ValueError('digits should be 6 or 8') if digits != 6: args[DIGITS] = digits args[PERIOD] = 30 account = quote(account) if issuer is not None: account = quote(issuer) + ':' + account args[ISSUER] = issuer uri = uri.format(type, account, urlencode(args).replace("+", "%20")) return uri