def sync_token(token, secret, session=requests, timestamp=None): '''Sync the generated token. This will fail for a TOTP token if performed less than 2 periods after the last sync or check.''' secret_hex = binascii.b2a_hex(secret).decode('ascii') if timestamp is None: timestamp = int(time.time()) if token.get('counter') is not None: # HOTP # This reliably fails with -1, 0 otp1 = hotp(secret_hex, counter=token['counter']) otp2 = hotp(secret_hex, counter=token['counter'] + 1) elif token.get('period'): # TOTP otp1 = totp(secret_hex, period=token['period'], t=timestamp - token['period']) otp2 = totp(secret_hex, period=token['period'], t=timestamp) else: # Assume TOTP with default period 30 (FIXME) otp1 = totp(secret_hex, t=timestamp - 30) otp2 = totp(secret_hex, t=timestamp) data = {'cr%s' % d: c for d, c in enumerate(otp1, 1)} data.update({'ncr%s' % d: c for d, c in enumerate(otp2, 1)}) data['cred'] = token['id'] data['continue'] = 'otp_sync' token_check = session.post(SYNC_URL, data=data) if "Your VIP Credential is successfully synced" in token_check.text: if token.get('counter') is not None: token['counter'] += 2 return True elif "Your VIP credential needs to be sync" in token_check.text: return False else: return None
def check_token(token_id, secret): '''Check the validity of the generated token.''' if token_id.startswith('VSMB'): otp = hotp(binascii.b2a_hex(secret),1).encode('utf-8') else: otp = totp(binascii.b2a_hex(secret)).encode('utf-8') test_url = 'https://idprotect.vip.symantec.com/otpCheck' token_check = requests.post( test_url, data=dict( cred=token_id, cr1=otp[0], cr2=otp[1], cr3=otp[2], cr4=otp[3], cr5=otp[4], cr6=otp[5], cr7="", count="1", ) ) if token_check.status_code != 200: sys.stderr.write("Bad token check url, " + token_check.status_code + "\n") return True if "Your VIP Credential is working correctly" in token_check.text: return True else: sys.stderr.write("bad otp \"" + otp + "\" : " + token_check.text + "\n") return False
def check_token(token_id, secret, session=requests): '''Check the validity of the generated token.''' test_url = 'https://vip.symantec.com/otpCheck' if token_id.startswith('VSMB'): print('Checking HOTP token with Counter=1') otp = hotp(binascii.b2a_hex(secret), 1).decode('utf-8') else: print('Checking TOTP token with Current Time') otp = totp(binascii.b2a_hex(secret).decode('utf-8')) token_check = session.post(test_url, data={ 'cr1': otp[0], 'cr2': otp[1], 'cr3': otp[2], 'cr4': otp[3], 'cr5': otp[4], 'cr6': otp[5], 'cred': token_id, 'count': '1', 'continue': 'otp_check' }) if "Your VIP Credential is working correctly" in token_check.text: return True elif "Your VIP credential needs to be sync" in token_check.text: return False else: return None
def get_hotp(raw_seed, counter, token_type=None): """ Checks whether `auth_code` is a valid authentication code for `counter` based on the `raw_seed` (raw byte string representation of `seed`). """ if not token_type: token_type = DEFAULT_TOKEN_TYPE return hotp( hexlify(raw_seed), counter, token_type, )
def test_accept_hotp(self): tvector2 = [ (0, '4c93cf18', '1284755224', '755224',), (1, '41397eea', '1094287082', '287082',), (2, '82fef30', '137359152', '359152',), (3, '66ef7655', '1726969429', '969429',), (4, '61c5938a', '1640338314', '338314',), (5, '33c083d4', '868254676', '254676',), (6, '7256c032', '1918287922', '287922',), (7, '4e5b397', '82162583', '162583',), (8, '2823443f', '673399871', '399871',), (9, '2679dc69', '645520489', '520489',),] for counter, hexa, deci, trunc in tvector2: h = hotp(self.secret, counter, format='hex') d = hotp(self.secret, counter, format='dec') d6 = hotp(self.secret, counter, format='dec6') self.assertEqual(d, deci) self.assertEqual(h, hexa) self.assertEqual(d6, trunc) self.assertTrue(accept_hotp(self.secret, trunc, counter))
def on_message(self,message): try: data = json.loads(message) except json.JSONDecodeError as e: tornado.log.gen_log.info("error loading json data " + str(e)) self.close(code=1003,reason=str(e)) tornado.log.gen_log.info(" message " + str(data)) sess = self.get_secure_cookie('sess') if 'type' not in data or data['type'] not in known_types or not sess: self.close(code=1003,reason='invalid message type') sess = sess.decode() res = {} if data['type'] == 'email': res['innermodal'] = "<div class='w3-input-group'><input id='content' class='w3-input' type='text' name='eotp' required><label class='w3-label w3-validate'>OTP</label></div><button onclick='submitcontent()' class='w3-btn w3-indigo w3-card-4'>Submit</button>" res['modalhead'] = "<h5>Enter OTP received in confirmation Email</h5>" res['type'] = "eotp" self.eotp = oath.hotp(sess,1) # TBD: send confirmation email with eotp tornado.log.gen_log.info('eotp ' + self.eotp) session = {'actual': data['email']} self.settings['redis'].set(sess,pickle.dumps(session)) self.write_message(json.dumps(res)) return elif data['type'] == "eotp": session = self.settings['redis'].get(sess) if not session or not hasattr(self,"eotp"): self.close(code=1003,reason='Invalid Session') session = pickle.loads(session) if self.eotp == data['eotp']: imgdata = yield self.settings['tpool'].submit(gen_data,self.eotp) res['innermodal'] = "<h5 class='w3-center'>SMS READDR " + self.eotp + "<br>to 9988776655</h5><div class='w3-container w3-hide-small w3-center'><h5>OR<br>Scan the below QRCODE</h5><img class='w3-image' style='max-height: 150px;' src='" + imgdata + "'/></div><h5 class='w3-center'>The page will auto-update as soon as we receive your SMS</h5>" res['modalhead'] = "<h5>Send SMS to Add Phone Number</h5>" res['type'] = "ssms" sockmap[self.eotp] = {'websock': self, 'sess': sess} self.write_message(json.dumps(res)) else: self.close(code=1003,reason='Wrong OTP') return elif data['type'] == "addr": session = self.settings['redis'].get(sess) if not session: self.close(code=1003,reason='Invalid Session') session = pickle.loads(session) session['address'] = data['addr'] session['mapped'] = session['mobile'] + '@readdess.io' yield self.settings['db'].users.save(session) self.settings['redis'].delete(sess) self.close(code=1000) return
def test_hotp(self): tvector = [ (0, 'cc93cf18508d94934c64b65d8ba7667fb7cde4b0'), (1, '75a48a19d4cbe100644e8ac1397eea747a2d33ab'), (2, '0bacb7fa082fef30782211938bc1c5e70416ff44'), (3, '66c28227d03a2d5529262ff016a1e6ef76557ece'), (4, 'a904c900a64b35909874b33e61c5938a8e15ed1c'), (5, 'a37e783d7b7233c083d4f62926c7a25f238d0316'), (6, 'bc9cd28561042c83f219324d3c607256c03272ae'), (7, 'a4fb960c0bc06e1eabb804e5b397cdc4b45596fa'), (8, '1b3c89f65e6c9e883012052823443f048b4332db'), (9, '1637409809a679dc698207310c8c7fc07290d9e5'), ] for counter, value in tvector: h = hotp(self.secret, counter, format='hex-notrunc') self.assertEqual(h, value.encode('ascii'))
def test_hotp(self): tvector = [ (0, 'cc93cf18508d94934c64b65d8ba7667fb7cde4b0'), (1, '75a48a19d4cbe100644e8ac1397eea747a2d33ab'), (2, '0bacb7fa082fef30782211938bc1c5e70416ff44'), (3, '66c28227d03a2d5529262ff016a1e6ef76557ece'), (4, 'a904c900a64b35909874b33e61c5938a8e15ed1c'), (5, 'a37e783d7b7233c083d4f62926c7a25f238d0316'), (6, 'bc9cd28561042c83f219324d3c607256c03272ae'), (7, 'a4fb960c0bc06e1eabb804e5b397cdc4b45596fa'), (8, '1b3c89f65e6c9e883012052823443f048b4332db'), (9, '1637409809a679dc698207310c8c7fc07290d9e5'), ] for counter, value in tvector: h = hotp(self.secret, counter, format='hex-notrunc') self.assertEqual(h, value)
def check_token(token, secret, session=requests): '''Check the validity of the generated token.''' secret_b32 = binascii.b2a_hex(secret).decode('ascii') if token.get('counter') is not None: # HOTP otp = hotp(secret_b32, counter=token['counter']) elif token.get('period'): # TOTP otp = totp(secret_b32, period=token['period']) else: # Assume TOTP with default period 30 (FIXME) otp = totp(secret_b32) data = {'cr%s'%d:c for d,c in enumerate(otp, 1)} data['cred'] = token['id'] data['continue'] = 'otp_check' token_check = session.post(TEST_URL, data=data) if "Your VIP Credential is working correctly" in token_check.text: if token.get('counter') is not None: token['counter'] += 1 return True elif "Your VIP credential needs to be sync" in token_check.text: return False else: return None
def totp(key, format='dec6', period=30, t=None, hash=hashlib.sha1): ''' Compute a TOTP value as prescribed by OATH specifications. :param key: the TOTP key given as an hexadecimal string :param format: the output format, can be: - hex40, for a 40 characters hexadecimal format, - dec4, for a 4 characters decimal format, - dec6, - dec7, or - dec8 it default to dec6. :param period: a positive integer giving the period between changes of the OTP value, as seconds, it defaults to 30. :param t: a positive integer giving the current time as seconds since EPOCH (1st January 1970 at 00:00 GMT), if None we use time.time(); it defaults to None; :param hash: the hash module (usually from the hashlib package) to use, it defaults to hashlib.sha1. :returns: a string representation of the OTP value (as instructed by the format parameter). :type: str ''' if t is None: t = int(time.time()) else: if isinstance(t, datetime.datetime): t = calendar.timegm(t.utctimetuple()) else: t = int(t) T = int(t/period) return hotp(key, T, format=format, hash=hash)
db = conn.redrdb tokens = set() counter = 0 while len(tokens) < ((26**4)*0.9): token = newtempname() if token not in tokens: tokens.add(token) seed = random.randint(0,(10**6)*0.9) tdata = {'token': token, "tokenid": counter, "usecount": seed, 'seed': seed} db.tokens.insert(tdata) print(str(tdata)) counter = counter + 1 key = uuid.uuid4().hex pins = set() attempts = 1 counter = 0 while len(pins) < ((10**6)*0.9): pin = oath.hotp(key,attempts) if pin not in pins: pins.add(pin) db.pins.insert({"pin": pin, "pinid": counter}) print('added pin ' + pin + ' pinid ' + str(counter)) counter = counter + 1 attempts = attempts + 1 print('Done.')
def test_dec8_regression_20130716(self): h = hotp("fb9cda921c82d893d9cdc6d6559997b1","132974666","dec8") assert len(h) == 8, 'wrong length %s' % h assert h == '03562487'
def test_accept_hotp(self): tvector2 = [ ( 0, '4c93cf18', '1284755224', '755224', ), ( 1, '41397eea', '1094287082', '287082', ), ( 2, '82fef30', '137359152', '359152', ), ( 3, '66ef7655', '1726969429', '969429', ), ( 4, '61c5938a', '1640338314', '338314', ), ( 5, '33c083d4', '868254676', '254676', ), ( 6, '7256c032', '1918287922', '287922', ), ( 7, '4e5b397', '82162583', '162583', ), ( 8, '2823443f', '673399871', '399871', ), ( 9, '2679dc69', '645520489', '520489', ), ] for counter, hexa, deci, trunc in tvector2: h = hotp(self.secret, counter, format='hex') d = hotp(self.secret, counter, format='dec') d6 = hotp(self.secret, counter, format='dec6') self.assertEqual(d, deci) self.assertEqual(h, hexa) self.assertEqual(d6, trunc) self.assertTrue(accept_hotp(self.secret, trunc, counter))
def test_dec8_regression_20130716(self): h = hotp("fb9cda921c82d893d9cdc6d6559997b1", "132974666", "dec8") assert len(h) == 8, 'wrong length %s' % h assert h == '03562487'
def accept_hotp(key, response, counter, format='dec6', hash=hashlib.sha1, drift=3, backward_drift=0): ''' Validate a HOTP value inside a window of [counter-backward_drift:counter+forward_drift] :param key: the shared secret :type key: hexadecimal string of even length :param response: the OTP to check :type response: ASCII string :param counter: value of the counter running inside an HOTP token, usually it is just the count of HOTP value accepted so far for a given shared secret; see the specifications of HOTP for more details; :param format: the output format, can be: - hex40, for a 40 characters hexadecimal format, - dec4, for a 4 characters decimal format, - dec6, - dec7, or - dec8 it defaults to dec6. :param hash: the hash module (usually from the hashlib package) to use, it defaults to hashlib.sha1. :param drift: how far we can look forward from the current value of the counter :param backward_drift: how far we can look backward from the current counter value to match the response, default to zero as it is usually a bad idea to look backward as the counter is only advanced when a valid value is checked (and so the counter on the token side should have been incremented too) :returns: a pair of a boolean and an integer: - first is True if the response is validated and False otherwise, - second is the new value for the counter; it can be more than counter + 1 if the drift window was used; you must store it if the response was validated. >>> accept_hotp('343434', '122323', 2, format='dec6') (False, 2) >>> hotp('343434', 2, format='dec6') '791903' >>> accept_hotp('343434', '791903', 2, format='dec6') (True, 3) >>> hotp('343434', 3, format='dec6') '907279' >>> accept_hotp('343434', '907279', 2, format='dec6') (True, 4) ''' for i in range(-backward_drift, drift+1): if hotp(key, counter+i, format=format, hash=hash) == str(response): return True, counter+i+1 return False,counter