Wednesday, 16 March 2011

Upgrade seed file

In DualShield, you may encounter a problem when importing a Deepnet token seed file which came with its previous version v3.x, as they changed the format of token seed file. If the file only contains a few tokens, you can modify it manually, however it is very hard labour if it contains hundred tokens.


Here is the utility to convert from v3x format to v5x format, also the source code, it utilizes pugixml to manipulate the xml file.


The command to run this utility is, seedupgrade infile outfile.




V3x Format



<tokenList>
<tokenInfo>
<method>PocketID</method>
<device>
<manufacturer>Deepnet Security</manufacturer>
<vendor>Deepnet Security</vendor>
<model>CB</model>
<serial>71358472</serial>
</device>
<token><![CDATA[<data>
<deviceID>71358472</deviceID>
<type>CB</type>
<counter>0</counter>
<digits>6</digits>
<secret enc="HEX">08E49538328BAE830E0DB14A82341100BC23B840</secret>
<description>71358472</description>
</data>
]]></token>
</tokenInfo>
</tokenList>



V5x Format



<?xml version="1.0"?>
<data>
<header>
<manufacturerCode>DN</manufacturerCode>
<productCode>ST</productCode>
<encode>HEX</encode>
<encrypt>NONE</encrypt>
</header>
<tokens>
<token>
<serial>71358472</serial>
<seed>08E49538328BAE830E0DB14A82341100BC23B840</seed>
</token>
</tokens>
</data>

Add an additional SMS provider

Deepnet Authentication Server(since v3.4x) has a good design on the support of SMS provider. It is very flexible to do the customization - In theory, you can add any SMS provider you like. It is a very good feature, as the domestic SMS providers are always cheaper than the world famous one like ClickAtell.


Then how? It seems Deepnet has no document to describe the procedures. I can fully understand it, as the template heavily depends on a particular SMS provider. First of all, you have to get the API document of your SMS provider, make sure what protocol they are using, HTTP, SMTP, or SMPP etc.


Next, study the built-in SMS provider templates. You can find them in the folder "$INSTALL_PATH/Tomcat/conf/dgs" ("$INSTALL_PATH/smsproviders" if you are using v5.x).


Congratulations if your provider operates as same as one of the built-in SMS providers, you are lucky.


Assume you are going to use "txtmail.co.nz" as your SMS provider in Deepnet, then copy the built-in provider "gin.xml", and save the new file as txtmail.xml, open the file "txtmail.xml" and change the provider name etc. At the end, the file content shoull look like the following



<?xml version="1.0" encoding="UTF-8"?>
<provider name="txtmail">
<sms>
<smtp>
<email>${to}@txtmail.co.nz</email>
<format>text</format>
<from>${from}</from>
<subject>${subject}</subject>
<body><![CDATA[${text}]]></body>
</smtp>
</sms>
</provider>

Finally, Add an entry in the file "sms_providers.xml",
<provider name="TXTMail" file="txtmail.xml" />


Access the management console of Deepnet, you should see the newly added provider in SMS Gateway Settings.


Here is another example, it has not been tested yet though. mollie.nl can send SMS via HTTP.


The syntax of sending the request is,

http://www.mollie.nl/xml/sms/?username=[username] &password=[password]&originator=[originator]&recipients=[recipient(s)]&message=[message]


And the response looks like the following


<?xml version="1.0" ?>
<response>
<item type="sms">
<recipients>1</recipients>
<success>true</success>
<resultcode>10</resultcode>
<resultmessage>Message successfully sent.</resultmessage>
</item>
</response>


As it is a HTTP protocol, you can refer to the built-in ClickAtell. The response is XML format, so you can use xmlPath to extract the result, otherwise, you may need a regular expression which is complicated. However you can find the regular expression example in the built-in templates. The final mollie template would be




<provider name="Mollie">
<sms>
<http>
<method>GET</method>
<url>http://www.mollie.nl/xml/sms/</url>
<query>username=${userName}&password=${password}&originator=${from}&recipients=${to}&message=${body}</query>
<variables>
<variable name="flash"/>
<variable name="dlr_url"/>
</variables>
<mappings>
<mapping name="replace_sms" type="boolean">
<true>1</true>
<false>0</false>
</mapping>
</mappings>
<response type="http_body" regexp="true" format="xml">
<pattern success="true" xmlPath="//response/item/success">true</pattern>
</response>
</http>
<smtp>
<email>${to}@mobiel.mollie.nl</email>
<format>text</format>
<from>${from}</from>
<subject>username=${userName}&password=${password}&sender=${subject}</subject>
<body><![CDATA[${text}]]></body>
</smtp>
</sms>
</provider>

mollie.nl also supports SMTP. It is quite interesting to see how it does the authentication - credentials in Subject!

Monday, 14 March 2011

changes log nsis plug-in

I tried to use CustomLicense plugin to load the changes log in my update installer, it never worked with NSIS 2.46 (the exported function LoadFile was never called, confirmed by adding a MessageBox in this function). The size of the plugin dll also puzzled me, it is just 4K (4096 bytes, no more, no less).


Couldn't figure out why, so I build it myslef, using "exdll.h" instead of "pluginapi.h". My DLL is much bigger(44K), it works though.

Thursday, 10 March 2011

Learning ISA

In this document, we try to use two options(web publishing and server publishing rule) to publish the same MS SharePoint(WSS30) site. It also tries to show how to setup filewall rule to allow ftp (from ISA machine to outside) and RDP(outside to ISA machine).

Friday, 18 February 2011

LDAP + OTP over CHAP

Many peope wish they can use LDAP Password + OTP in RADIUS authentication to enhance their security. MSCHAP(CHAP) is defacto the common protocol used in RADIUS, as PAP is denounced due to its plain text transmission across the Internet. However it is technically impossible for LDAP + OTP over CHAP. Why? you will certainly ask the same question as I did when I first dealt with it. Basically, you can regard CHAP as a hash algorithm like the popular MD5 or SHA1. In mathematics,


HASH(StaticPass + OTP) != HASH(StaticPass) + HASH(OTP)


At VPN client side, you input StaticPass + OTP, at VPN server side, the server receives HASH(StaticPass + OTP). If the server has no plain text of StaticPass, then it is impossible for the server to decide if there is a match. Unfortunately LDAP password is not available as a plain text. According to MS, the password itself is actually a LM hash. As a result, none of any two factor authentiction service providers can support LDAP + OTP over CHAP. If you know any vendors who claim they support this, please let me know.


Here is the summary of Deepnet Radius Authentication. X = supported.


In the situation where StaticPass token doesn't physically exist in Deepent database, LDAP password is used instead.
































MSCHAP2(CHAP)PAP
StaticPass
X
OTP
X1
X
StaticPass + OTP
X
StaticPass >> OTP
X


If a StaticPass token exists, the password can be retrieved in Deepnet.

































MSCHAP2(CHAP)PAP
StaticPass
X
X
OTP
X1
X
StaticPass + OTP
X1
X
StaticPass >> OTP
X1
X

Note: 1 All OTP tokens except vasco token.

Wednesday, 16 February 2011

Passing Username

Deepnet IIS solution adds an additional login page, that means the user has to type his username twice which is inconvenience. Is there any way to pass the username typed on the first login (deepnet) page down to the second login(the original) page?


The answer is YES. Let's take Microsoft Remote Web Workplace in SBS as an example, find the related file "logon.aspx", locate the line



<TD class="linkText"><INPUT id="txtUserName" tabIndex="1" type="text" name="txtUserName" ></TD>


change it to



<TD class="linkText"><INPUT id="txtUserName" tabIndex="1" type="text" name="txtUserName" value='<%=Request.Cookies["DasUserName"] == null ? string.Empty : Request.Cookies["DasUserName"].Value%>' ></TD>


As you can see, the value attribute is added: value='<%=Request.Cookies["DasUserName"] == null ? string.Empty : Request.Cookies["DasUserName"].Value%>'


If the object is run at server (runat="server"), then you should change it to be like



<td align=right class=linkText><%=loadResString("L_LOGON_USER_NAME")%></td>
<TD class="linkText"><INPUT id="txtUserName" tabIndex="1" type="text" name="txtUserName" runat="server"></TD>
<td class="linkText"></td>
<script type="text/javascript">
<!-- //
var cookie = GetDASCookie('DasUserName');
document.getElementById('<%=txtUserName.ClientID%>').value = cookie.split('=')[1];
//-->
</script>



Furthermore, in order to prevent the field from editing, you can add readonly attribute.



<TD class="linkText"><INPUT id="txtUserName" tabIndex="1" type="text" name="txtUserName" readonly="readonly" value='<%=Request.Cookies["DasUserName"] == null ? string.Empty : Request.Cookies["DasUserName"].Value%>' runat="server"></TD>

Deepnet mandate option on ActiveSync

Somehow the mandate option in Deepnet IIS solution doesn't work on ActiveSync protection. Once you add the deepnet protection on ActiveSync node, then each mobile device which synchronizes with Exchange server will need two-factor authentication, no matter you check on/off the mandate option.


In testing phase, It's quite likely that you want one or two devices to be protected with Deepnet 2-factor authentication while the others still work with the original one factor(Windows username/password).


Well, how to achieve this purpose? Here it is the workaround.


After assigning the event filter(see the related user guide from Deepnet), edit its content. The following python script will function as non-mandate option on deepnet ActiveSync protection.



"""
This is a trigger script used by DAS

# return integer:
# zero: continue trigger chain
# nonzero: quit chain

# return boolean
# False: continue trigger chain
# True: quit chain

# return dictionary
# dict['noContinue']
# dict['noLog']
# dict['newReturnVal']
#
"""
#import socket
import sys
import java.util.Hashtable

def Log(msg):
return
# HOST = '192.168.111.168' # The remote host
# PORT = 7000 # The same port as used by the server
# s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# s.connect((HOST, PORT))
# s.send(str(msg) +'\r\n')
# s.close()

class Trigger:
def __init__(self):
pass

def execute(self, *args, **dict):

try:
if filter_context is None:
return 0
except:
return 0

Log('------------------------')
#Log(filter_context['DasAPI'].hello()[0])
Log(filter_context['appID'])
Log(filter_context['resultCode'])

for i in filter_context.keys():
#Log(i)
#Log(filter_context[i])
Log(i + ': ' + str(filter_context[i]))

if not filter_context.containsKey('method'):
return 0

if filter_context['method'].lower()!='devicepass':
return 0

Log('DevicePass found')

# if user account not found, try to import it from LDAP
if filter_context['resultCode']=='E-USERID-NOT-FOUND' or filter_context['resultCode']=='E-LOGINNAME-NOT-FOUND':
# bRet = self.importAccount()

# if bRet==True:
# bRet = self.createToken()
# if bRet==True:
# return self.changeReturn('E-TOKEN-INACTIVE')
return self.changeReturn('TRUE')

if filter_context['resultCode']=='E-TOKEN-NOT-FOUND' or filter_context['resultCode']=='E-METHOD-NOT-FOUND':
bRet = self.createToken()
if bRet==True:
return self.changeReturn('E-TOKEN-INACTIVE')

return 0

def importAccount(self):
try:
result = filter_context['DasAPI'].importAccount(
filter_context['appID'], filter_context['loginName'])
Log( 'importAccount: ' + str(result) )
if result[0]=='OK':
self.newUserID = str(result[1])
return True
return False
except:
Log(sys.exc_info()[0])
return False

def createToken(self):
try:
tokenInfo = java.util.Hashtable()
tokenInfo['credential'] = filter_context['credential']
tokenInfo['method'] = filter_context['method']
tokenInfo['description'] = 'created by trigger'
userID = filter_context['userID']
if userID=='':
userID = self.newUserID

result = filter_context['DasAPI'].createToken(
filter_context['appID'],
userID, tokenInfo, False)
Log( 'createToken: ' + str(result) )
if result[0]=='OK':
return True
return False
except:
Log(sys.exc_info()[0])
return False;


def changeReturn(self, val):
r={'newReturnVal':val}
return r


def main():
trigger = Trigger()
return trigger.execute()

if __name__ == '__main__':
main()

If you compare it with the original python script, you will see the difference.



#original
if filter_context['resultCode']=='E-USERID-NOT-FOUND' or filter_context['resultCode']=='E-LOGINNAME-NOT-FOUND':
bRet = self.importAccount()

if bRet==True:
bRet = self.createToken()
if bRet==True:
return self.changeReturn('E-TOKEN-INACTIVE')
return 0

#new
if filter_context['resultCode']=='E-USERID-NOT-FOUND' or filter_context['resultCode']=='E-LOGINNAME-NOT-FOUND':
# bRet = self.importAccount()

# if bRet==True:
# bRet = self.createToken()
# if bRet==True:
# return self.changeReturn('E-TOKEN-INACTIVE')
return self.changeReturn('TRUE')