TUTORIAL Add a One time Password (TOTP) to your dial plan

DadinVa

New Member
Joined
Apr 20, 2016
Messages
10
Reaction score
2
You can significantly reduce the potential for high fraudulent long distance charges by requiring a password be entered before the call is allowed. But why use a password when you can use a one time password (OTP) which changes every 30 seconds. All you need is for your users to have an ios or android, tablet or phone and the following changes on your asterisk machine!

The following code can be downloaded from https://sourceforge.net/projects/asterisk-otp-in-python/files

The steps are:
1. As root copy code shown below to create python program otpcode.py which will calculate one time passwords given a secret base32 password seed.
2. As root copy code shown below to create python program getotp.py which will act as a simple interface between asterisk and the otpcode program
3. As root copy code shown below to create get_base32_pwd which will generate base32 passwords to use. This secret base32 password will be stored on the asterisk machine and in your phone in step 5.
4. Add to your asterisk extensions a few lines of code to verify the OTP entered by the caller matches the OTP calculated by the python program
5. Install on your phone freeotp, an open source program that will calculate the OTP you will need to send to the asterisk machine.

1. using vi editor create new file by copying and pasting following code:
cd /etc/asterisk
vi /etc/asterisk/otpcode.py
i
#Portions Copyright (c) 2013, Twilio, Inc. from the site https://github.com/jpf/Twilio-TFA
# Based on the pyotp: https://github.com/nathforge/pyotp
#
#Portions Copyright (C) 2011 by Mark Percival <[email protected]>
#
#Python port copyright 2011 by Nathan Reynolds <[email protected]>
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in
#all copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.

import base64
import hashlib
import hmac
import datetime
import time
import sys
import random as _random

class ASECRET(object):
def __init__(self, length=16):
self.length = length

def random_base32(self, length=16, random=_random.SystemRandom(),
chars=list('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')):
return ''.join(
random.choice(chars)
for _ in range(length)
)

class OTP(object):
def __init__(self, secret, digits=6):
self.secret = secret

def int_to_bytestring(self, int, padding=8):
"""
Turns an integer to the OATH specified
bytestring, which is fed to the HMAC
along with the secret
"""
result = []
while int != 0:
result.append(chr(int & 0xFF))
int = int >> 8
return ''.join(reversed(result)).rjust(padding, '\0')

def generate_otp(self, input):
"""
@param [Integer] input the number used seed the HMAC
Usually either the counter, or the computed integer
based on the Unix timestamp
"""
hmac_hash = hmac.new(
base64.b32decode(self.secret, casefold=True),
self.int_to_bytestring(input),
hashlib.sha1,
).digest()

offset = ord(hmac_hash[19]) & 0xf
code = ((ord(hmac_hash[offset]) & 0x7f) << 24 |
(ord(hmac_hash[offset + 1]) & 0xff) << 16 |
(ord(hmac_hash[offset + 2]) & 0xff) << 8 |
(ord(hmac_hash[offset + 3]) & 0xff))
# '6' is number of integers in the OTP
return code % 10 ** 6


class TOTP(OTP):
def __init__(self, *args, **kwargs):
"""
@Option options [Integer] internval (30) the interval in seconds
This defaults to 30 which is standard.
"""
self.interval = kwargs.pop('interval', 30)
super(TOTP, self).__init__(*args, **kwargs)

def timecode(self, for_time):
i = time.mktime(for_time.timetuple())
return int(i / self.interval)

def now(self):
"""
Generate the current time OTP
@return [Integer] the OTP as an integer
"""
return self.generate_otp(self.timecode(datetime.datetime.now()))

def at(self, date):
"""
Generate the current time OTP
@return [Integer] the OTP as an integer
"""
return self.generate_otp(self.timecode(date))

if __name__ == "__main__":
secret = "AAAAAAAAAAAAAAAA"
unixtime = 0
if len(sys.argv) > 1:
unixtime = int(sys.argv[1])
if unixtime > 1:
date = datetime.datetime.fromtimestamp(unixtime)
else:
date = datetime.datetime.now()
secret = ASECRET().random_base32()
print secret
totp = TOTP(secret)
print "TOTP token for secret '%s' at '%s' is: %s" % (
secret, date, totp.at(date))


press Esc key
press enter then type
:w press enter
:1 press enter then press
dd
:wq! press enter
at the bash prompt type
chmod 755 /etc/asterisk/otpcode.py
chown asterisk.asterisk otpcode.py

echo Step 2. As root use vi editor to add code for file to interface with asterisk
cd /etc/asterisk
vi getotp.py
i
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'dadinVa'

import sys
import datetime
from otpcode import *

def log(msg):
open('/tmp/getotp.log','ab+',1512).write(
"%s: %s\n"%(datetime.datetime.now(),msg)
)

totalargs = len(sys.argv)
otpoffset = "none"
if totalargs > 1:
otppassword = sys.argv[1]
log(sys.argv[1])
# I could not get args passed in from asterisk to work correctly so this is the fix
if "," in otppassword:
otpoffset = otppassword.split(',')[1]
log("otpoffsetis ")
log(otpoffset)
otppassword = otppassword.split(',')[0].rstrip('\n')
log("otppassword ")
log(otppassword)
#End of fix I could not get args passd in from asterisk to work correctly
if totalargs <2 or otppassword == "help":
print "for a one time password the syntax is:"
print "./getotp.py a_secret_base32_password timeoffset(optional)"
print " examples are:"
print "./getotp.py \"GEZDGNBVGY3TQOJQ\""
print "./getotp.py GEZDGNBVGY3TQOJQ"
print "./getotp.py \"GEZDGNBVGY3TQOJQ\" -30"
print "./getotp.py \"GEZDGNBVGY3TQOJQ, -30\""
print "Do not use GEZDGNBVGY3TQOJQ as your base password instead "
print "for a new secret password enter ./getotp.py new_password"
sys.exit(200)
if otppassword == "new_password":
print ("%s" % str(ASECRET().random_base32()))
sys.exit(0)
timeoffset = 0
if totalargs >2:
timeoffset = int(sys.argv[2])
if otpoffset <> "none":
timeoffset = int(otpoffset)
log("time out is ")
log(str(timeoffset))
for_time = datetime.datetime.now() + datetime.timedelta(0,timeoffset)
print "%s" % TOTP(otppassword).at(for_time)
sys.exit(0)

(Press escape)
(then delete the top blank line by entering)
:1
dd
:wq!

at the bash prompt paste or type the following:
chmod 755 getotp.py
chown asterisk.asterisk getotp.py
echo The following are tests
./getotp.py MXVJEWUQGK3RKGMO
./getotp.py MXVJEWUQGK3RKGMO -60
./getotp.py "MXVJEWUQGK3RKGMO,-60"
./getotp.py new_password


the Asterisk portion will be covered in the reply section
 
Last edited:

DadinVa

New Member
Joined
Apr 20, 2016
Messages
10
Reaction score
2
echo Step 3 - copy and paste following
echo Now create executable to create passwords
vi /root/get_base32_pwd
i
/etc/asterisk/getotp.py new_password
(press escape)
(then delete the top blank line by entering)
:1
dd
:wq!
chmod 755 /root/get_base32_pwd

Now enter /root/get_base32_pwd

You will need to enter this new password into the database or put it into the file
/etc/asterisk/${MYACCTNO}S.base
also put a 3 digit pin in the file /etc/asterisk/${MYACCTNO}.base

For example if the account number is 12345678
create a file /etc/asterisk/12345678S.base and put the Secret base32 password in it. Then create a file /etc/asterisk/12345678.base and put a 3 digit number in it such as 246 this will be the users PIN.

You will also need to enter the new Secret base32 password in your phone in step 5. If you e-mail it to users, make sure they delete the email after they have finished setting up their phone.

Step 4.
Add the following code to your Asterisk dial plan to use on extension 563

; verify PIN and One Time Password (OTP)
exten => 563,n,Set(MYACCTNO=${CHANNEL(peername)})
exten => 563,n,NoOp(ACCTNO: ${MYACCTNO})
exten => 563,n,Set(ACCTNO=${MYACCTNO})
exten => 563,n,Set(OTPSEED=${FILTER(0-9A-Za-z,${FILE(/etc/asterisk/${MYACCTNO}S.base)})})
exten => 563,n,Set(PIN=${FILTER(0-9A-Za-z,${FILE(/etc/asterisk/${MYACCTNO}.base)})})
;
;exten => 563,n,Set(USERDATAS=${ODBC_TRAVMAN4(${MYACCTNO}S)})
;exten => 563,n,GotoIf($["${USERDATAS},foo" = ",foo"]?notfound)
;exten => 563,n,Set(ACCTNO=${CUT(USERDATAS,\,,1)})
;exten => 563,n,Set(OTPSEED=${FILTER(0-9A-Za-z,${CUT(USERDATAS,\,,2)})})
;exten => 563,n,Set(USERDATA=${ODBC_TRAVMAN4(${MYACCTNO})})
;exten => 563,n,GotoIf($["${USERDATA},foo" = ",foo"]?notfound)
;exten => 563,n,Set(ACCTNO2=${CUT(USERDATA,\,,1)})
;exten => 563,n,Set(PIN=${FILTER(0-9A-Za-z,${CUT(USERDATA,\,,2)})})
;
exten => 563,n,GotoIf($["${OTPSEED},foo" = ",foo"]?notfound)
exten => 563,n,Set(USEDOTP=${FILE(/etc/asterisk/${MYACCTNO}.usedOTP)})
exten => 563,n,Set(PWDTRIES=0)
exten => 563,n(trypwd),Set(PWDTRIES=$[${PWDTRIES} +1])
exten => 563,n,Flite("Please enter your PIN.")
exten => 563,n,Wait(1)
exten => 563,n,Read(MYPIN,beep,3)
exten => 563,n,NoOp(PIN: ${MYPIN})
exten => 563,n,Flite("Please enter your one time password.")
exten => 563,n,Wait(1)
exten => 563,n,Read(MYOTP,beep,6)
exten => 563,n,GotoIf($["${MYOTP},foo" = ",foo"]?nexttry)
; to try to reduce Password attacks
; Prevent reuse of previously valid password-
exten => 563,n,GotoIf($["${MYOTP},foo" = "${USEDOTP},foo"]?notfound)
exten => 563,n,NoOp(myotp: ${MYOTP})
;Although valid password is 6 digit number for security following section truncates to 9 digit number with letters and numbers
; check if password was created 60-90 seconds ago
exten => 563,n,Set(VALIDOTP=${FILTER(0-9A-Za-z,${SHELL(/etc/asterisk/getotp.py ${OTPSEED} -60):0:9})})
exten => 563,n,GotoIf($["${MYPIN}${MYOTP}" = "${PIN}${VALIDOTP}"]?otpok)
; check if password was created 30-60 seconds ago
exten => 563,n,Set(VALIDOTP=${FILTER(0-9A-Za-z,${SHELL(/etc/asterisk/getotp.py ${OTPSEED} -30):0:9})})
exten => 563,n,GotoIf($["${MYPIN}${MYOTP}" = "${PIN}${VALIDOTP}"]?otpok)
; check if password was created 15-45 seconds ago
exten => 563,n,Set(VALIDOTP=${FILTER(0-9A-Za-z,${SHELL(/etc/asterisk/getotp.py ${OTPSEED} -15):0:9})})
exten => 563,n,GotoIf($["${MYPIN}${MYOTP}" = "${PIN}${VALIDOTP}"]?otpok)
; check if one time password was created 0-30 seconds ago
;exten => 563,n,AGI(AGIgetotp.py,${OTPSEED},0)
exten => 563,n,Set(VALIDOTP=${FILTER(0-9A-Za-z,${SHELL(/etc/asterisk/getotp.py ${OTPSEED}):0:9})})
exten => 563,n,GotoIf($["${MYPIN}${MYOTP}" = "${PIN}${VALIDOTP}"]?otpok)
; allow three tries
exten => 563,n(nexttry),GotoIf($[${PWDTRIES} < 3]?trypwd)
exten => 563,n(notfound),Flite("Please call back")
exten => 563,n,Wait(1)
exten => 563,n,Hangup
exten => 563,n(otpok),Set(FILE(/etc/asterisk/${ACCTNO}.usedOTP)=${MYOTP})



Step 5. On your android device go to playstore and enter freeotp. It is also available on ios devices.
Install it, then open it. Press the symbol key + . Then on the first line, in the email entry just enter what you want the asterisk box to be called . For this example enter "asterisk machine". On the next line press the space bar. On the third line for secret enter the number you got from the asterisk machine when you ran get_base32_pwd. Leave type as TOTP and leave DIGITS as 6, also leave algorithm as SHA1 and leave interval as 30. Now press add. On the new screen touch the word asterisk machine. The symbol shows you how long until the one time password is invalid and the number shown is the one time password you need to enter into asterisk.
Currently in asterisk the one time password is valid for 90 seconds. For security the same password can not be reused. If you want to shorten how long a password is valid for, comment out or delete the lines of code which are commented in the asterisk dial plan.

Hopefully someone can create an AGI script based on this and post it. Also this could be modified to a macro for the dial plan.
 
Last edited:

Members online

No members online now.

Forum statistics

Threads
25,802
Messages
167,720
Members
19,232
Latest member
voiplads
Get 3CX - Absolutely Free!

Link up your team and customers Phone System Live Chat Video Conferencing

Hosted or Self-managed. Up to 10 users free forever. No credit card. Try risk free.

3CX
A 3CX Account with that email already exists. You will be redirected to the Customer Portal to sign in or reset your password if you've forgotten it.
Top