- Added gui.

This commit is contained in:
ccremers 2006-08-02 12:59:57 +00:00
parent 460c087cf2
commit e1ddf0668b
26 changed files with 2701 additions and 0 deletions

52
gui/Attack.py Normal file
View File

@ -0,0 +1,52 @@
#
# Attack
#
import Trace
import Term
#import Classification
from Misc import *
class Attack(object):
def __init__(self):
self.broken = []
self.match = None
self.initialKnowledge = []
self.inverseKeys = []
self.protocol = None
self.semiTrace = Trace.SemiTrace()
self.variables = []
self.protocoldescr = {}
self.id = None
self.knowledge = None
self.untrusted = []
self.typeflaws = False
self.commandline = ''
self.scytherDot = None
self.claim = None # refers to parent claim
def getInvolvedAgents(self):
result = []
for run in self.semiTrace.runs:
for agent in run.roleAgents.values():
result.append(agent)
return uniq(result)
def buildKnowledge(self):
if not self.knowledge:
self.knowledge = Term.Knowledge(self)
self.knowledge.buildKnowledge()
def getPrecedingLabelSet(self,event):
return self.protocoldescr[str(event.label[0])].getPrecedingLabelSet(event.label)
def getPrecedingRoleSet(self,event):
return self.protocoldescr[str(event.label[0])].getPrecedingRoleSet(event.label)
#def classify(self):
# classification = Classification.Classification(self)
# classification.classifyClaims()
# classification.classifyInitiations()
# classification.classifyComplexity()
# classification.classifyTypeflaws()
# return classification

41
gui/Attackimage.py Normal file
View File

@ -0,0 +1,41 @@
#!/usr/bin/python
#---------------------------------------------------------------------------
""" Import externals """
import os
#---------------------------------------------------------------------------
""" Import scyther-gui components """
import Tempfile
#---------------------------------------------------------------------------
class AttackImage:
def __init__(self,dotdata):
self.dotdata = dotdata
self.png = ""
self.MakeImage()
def MakeImage(self):
""" Sets png """
(fd,fpname) = Tempfile.tempcleaned(".dot")
fp = os.fdopen(fd, "w")
fp.write(self.dotdata)
fp.close()
(fd2,fpname2) = Tempfile.tempcleaned(".png")
os.system("dot %s -Tpng >%s" % (fpname, fpname2))
self.png = fpname2
Tempfile.tempcleanearly((fd,fpname))
def GetImage(self):
return self.png
#---------------------------------------------------------------------------

169
gui/Attackwindow.py Normal file
View File

@ -0,0 +1,169 @@
#!/usr/bin/python
#---------------------------------------------------------------------------
""" Import externals """
import wx
import time
#---------------------------------------------------------------------------
""" Import scyther-gui components """
import Icon
#---------------------------------------------------------------------------
class AttackDisplay(wx.ScrolledWindow):
def __init__(self, daddy, parent, claim,attackid):
self.win = daddy
wx.ScrolledWindow.__init__(self,parent,id=-1)
# Wait for the attack to be computed
while attackid >= len(claim.attacks):
time.sleep(1)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Image = wx.StaticBitmap(self, -1, wx.EmptyBitmap(1,1))
self.box = wx.BoxSizer(wx.VERTICAL)
self.box.Add(self.Image,1,wx.ALIGN_CENTER)
self.hbox = wx.BoxSizer(wx.HORIZONTAL)
self.hbox.Add(self.box,1,wx.ALIGN_CENTER)
self.SetSizer(self.hbox)
filename = claim.attacks[attackid].GetImage()
self.original = wx.Image(filename,wx.BITMAP_TYPE_PNG)
self.update()
# TODO self.Bind(wxSizeEvent
def OnSize(self,event):
self.update()
event.Skip()
def update(self):
self.SetScrollbars(0,0,0,0,0,0)
bmp = self.original
if self.win.fit:
W = bmp.GetWidth()
H = bmp.GetHeight()
(sw,sh) = self.win.GetClientSizeTuple()
if W > sw:
# correct width
factor = float(sw) / W
W = sw
H = H * factor
if H > sh:
# correct height
factor = float(sh) / H
H = sh
W = W * factor
bmp = self.original.Scale(W,H)
self.Image.SetBitmap(wx.BitmapFromImage(bmp))
#self.box.SetItemMinSize(self.Image.GetContainingSizer())
self.box.Layout()
# wx.StaticBitmap(self, -1, bmp, (0, 0), (bmp.GetWidth(), bmp.GetHeight()))
step = 20
xn = int(bmp.GetWidth() / step) + 1
yn = int(bmp.GetHeight() / step) + 1
self.SetScrollbars(step,step,xn,yn,0,0)
self.Refresh()
class AttackWindow(wx.Frame):
def __init__(self,cl):
super(AttackWindow, self).__init__(None, size=(400,800))
self.claim = cl
self.fit = False
self.CreateInteriorWindowComponents()
self.CreateExteriorWindowComponents()
Icon.ScytherIcon(self)
self.SetTitle()
def SetTitle(self):
if self.claim.attackcount == 1:
tstr = "Attack"
else:
tstr = "Attacks"
tstr += " for claim %s" % str(self.claim)
super(AttackWindow, self).SetTitle(tstr)
def CreateInteriorWindowComponents(self):
''' Create "interior" window components. In this case it is the
attack picture. '''
self.displays=[]
if self.claim.attackcount <= 1:
# Just a single window
self.tabs = None
self.displays.append(AttackDisplay(self,self,self.claim,0))
else:
# Multiple tabs
self.tabs = wx.Notebook(self,-1)
for i in range(0,self.claim.attackcount):
disp = AttackDisplay(self,self.tabs,self.claim,i)
classname = "Class %i" % ((i+1))
self.tabs.AddPage(disp, classname)
self.displays.append(disp)
self.Show(1)
def CreateExteriorWindowComponents(self):
''' Create "exterior" window components, such as menu and status
bars '''
self.CreateStatusBar()
self.SetupToolBar()
def SetupToolBar(self):
tb = self.CreateToolBar(wx.TB_HORIZONTAL
| wx.NO_BORDER
| wx.TB_FLAT
| wx.TB_TEXT
)
# Add fit button
bmp = wx.ArtProvider_GetBitmap(wx.ART_MISSING_IMAGE,wx.ART_TOOLBAR,(20,20))
if not bmp.Ok():
bmp = wx.EmptyBitmap(20,20)
tb.AddCheckTool(wx.ID_ZOOM_FIT, bmp, bmp, 'Toggle zoom', 'Toggle zoom level')
self.Bind(wx.EVT_TOOL, self.OnFit, id=wx.ID_ZOOM_FIT)
# And shortcut
aTable = wx.AcceleratorTable([
(wx.ACCEL_NORMAL, ord('Z'), wx.ID_ZOOM_FIT)
])
self.SetAcceleratorTable(aTable)
def update(self):
for t in self.displays:
t.update()
def OnFit(self,event):
if self.fit:
self.fit = False
else:
self.fit = True
self.update()
def OnRealSize(self):
self.fit = False
self.update()

66
gui/Claim.py Normal file
View File

@ -0,0 +1,66 @@
#
# Claim
#
import Term
class Claim(object):
def __init__(self):
self.claimtype = None
self.label = None
self.protocol = None
self.role = None
self.parameter = None
self.failed = 0
self.count = 0
self.states = 0
self.complete = False
self.timebound = False
self.attacks = []
self.state = False # if true, it is a state, not an attack
# derived info
self.foundstates = False
self.foundproof = False
def analyze(self):
if str(self.claimtype) == 'Reachable':
self.state = True
if self.failed > 0:
self.foundstates = True
if self.complete:
self.foundproof = True
def stateName(self,count=1):
if self.state:
s = "state"
else:
s = "attack"
if count != 1:
s += "s"
return s
def __str__(self):
s = "claim "
s+= " " + str(self.protocol)
s+= " " + str(self.role)
# We need the rightmost thingy here
label = self.label
while isinstance(label,Term.TermTuple):
label = label[1]
s+= " " + str(label)
s+= " " + str(self.claimtype)
if self.parameter:
s+= " " + str(self.parameter)
# determine status
s+= " : %i " % (self.failed)
s+= self.stateName(self.failed)
if self.complete:
s+= " (complete)"
return s

23
gui/Icon.py Normal file
View File

@ -0,0 +1,23 @@
#!/usr/bin/python
#---------------------------------------------------------------------------
""" Import externals """
import wx
import os.path
#---------------------------------------------------------------------------
""" Import scyther-gui components """
import Misc
#---------------------------------------------------------------------------
def ScytherIcon(window):
""" Set a nice Scyther icon """
iconfile = Misc.mypath("scyther-gui-32.ico")
if os.path.isfile(iconfile):
icon = wx.Icon(iconfile,wx.BITMAP_TYPE_ICO)
window.SetIcon(icon)

399
gui/Mainwindow.py Normal file
View File

@ -0,0 +1,399 @@
#!/usr/bin/python
#---------------------------------------------------------------------------
""" Import externals """
import wx
import os.path
import sys
import wx.lib.mixins.listctrl as listmix
#---------------------------------------------------------------------------
""" Import scyther-gui components """
import Preference
import Attackwindow
import Scytherthread
import Icon
#---------------------------------------------------------------------------
""" Some constants """
ID_VERIFY = 100
ID_AUTOVERIFY = 101
ID_STATESPACE = 102
ID_CHECK = 103
#---------------------------------------------------------------------------
class MainWindow(wx.Frame):
def __init__(self, filename=''):
super(MainWindow, self).__init__(None, size=(600,800))
self.dirname = '.'
if filename != '' and os.path.isfile(filename):
self.filename = filename
self.load = True
else:
self.filename = 'noname.spdl'
self.load = False
Icon.ScytherIcon(self)
self.CreateInteriorWindowComponents()
self.CreateExteriorWindowComponents()
aTable = wx.AcceleratorTable([
#(wx.ACCEL_ALT, ord('X'), exitID),
(wx.ACCEL_CTRL, ord('W'), wx.ID_EXIT),
(wx.ACCEL_NORMAL, wx.WXK_F1,
ID_VERIFY),
(wx.ACCEL_NORMAL, wx.WXK_F2,
ID_STATESPACE),
(wx.ACCEL_NORMAL, wx.WXK_F5,
ID_CHECK),
(wx.ACCEL_NORMAL, wx.WXK_F6,
ID_AUTOVERIFY),
])
self.SetAcceleratorTable(aTable)
self.claimlist = []
self.pnglist = []
#self.SetTitle(self.title)
def CreateInteriorWindowComponents(self):
''' Create "interior" window components. In this case it is just a
simple multiline text control. '''
self.splitter = wx.SplitterWindow(self,-1)
self.splitter.daddy = self
# Top: input
self.top = wx.Notebook(self.splitter,-1)
self.control = wx.TextCtrl(self.top, style=wx.TE_MULTILINE)
if self.load:
textfile = open(os.path.join(self.dirname, self.filename), 'r')
self.control.SetValue(textfile.read())
os.chdir(self.dirname)
textfile.close()
self.top.AddPage(self.control,"Protocol")
self.settings = SettingsWindow(self.top,self)
self.top.AddPage(self.settings,"Settings")
# Bottom: output
self.bottom = wx.Notebook(self.splitter,-1)
self.report = SummaryWindow(self.bottom,self)
self.bottom.AddPage(self.report,"Claim summary")
self.errors = ErrorWindow(self.bottom,self)
self.bottom.AddPage(self.errors,"Detailed output")
#self.report.SetValue("Welcome.")
# Combine
self.splitter.SetMinimumPaneSize(20)
self.splitter.SplitHorizontally(self.top, self.bottom)
self.splitter.SetSashPosition(510)
self.Show(1)
def CreateExteriorWindowComponents(self):
''' Create "exterior" window components, such as menu and status
bar. '''
self.CreateMenus()
self.CreateStatusBar()
self.SetupToolBar()
self.SetTitle()
def SetupToolBar(self):
tb = self.CreateToolBar(wx.TB_HORIZONTAL
| wx.NO_BORDER
| wx.TB_FLAT
| wx.TB_TEXT
)
bmp = wx.ArtProvider_GetBitmap(wx.ART_EXECUTABLE_FILE,wx.ART_TOOLBAR,(20,20))
if not bmp.Ok():
bmp = wx.EmptyBitmap(20,20)
tb.AddSimpleTool(ID_VERIFY, bmp,"Verify","Verify claims")
self.Bind(wx.EVT_TOOL, self.OnVerify, id=ID_VERIFY)
tb.AddSimpleTool(ID_STATESPACE, bmp,"Statespace","Generate statespace for all roles")
self.Bind(wx.EVT_TOOL, self.OnStatespace, id=ID_STATESPACE)
tb.AddSeparator()
tb.AddSimpleTool(ID_CHECK, bmp,"Check","Check protocol")
self.Bind(wx.EVT_TOOL, self.OnCheck, id=ID_CHECK)
tb.AddSimpleTool(ID_AUTOVERIFY, bmp,"Default claims","Verify default claims")
self.Bind(wx.EVT_TOOL, self.OnAutoVerify, id=ID_AUTOVERIFY)
def CreateMenu(self, bar, name, list):
fileMenu = wx.Menu()
for id, label, helpText, handler in list:
if id == None:
fileMenu.AppendSeparator()
else:
item = fileMenu.Append(id, label, helpText)
self.Bind(wx.EVT_MENU, handler, item)
bar.Append(fileMenu, name) # Add the fileMenu to the MenuBar
def CreateMenus(self):
menuBar = wx.MenuBar()
self.CreateMenu(menuBar, '&File', [
(wx.ID_OPEN, '&Open', 'Open a new file', self.OnOpen),
(wx.ID_SAVE, '&Save', 'Save the current file', self.OnSave),
(wx.ID_SAVEAS, 'Save &As', 'Save the file under a different name',
self.OnSaveAs),
(None, None, None, None),
(wx.ID_EXIT, 'E&xit\tCTRL-W', 'Terminate the program',
self.OnExit)])
self.CreateMenu(menuBar, '&Verify',
[(ID_VERIFY, '&Verify protocol\tF1','Verify the protocol in the buffer using Scyther',
self.OnVerify) ,
(ID_STATESPACE, 'Generate &statespace\tF2','TODO' ,
self.OnAutoVerify) ,
(None, None, None, None),
(ID_CHECK, '&Check protocol\tF5','TODO',
self.OnStatespace) ,
(ID_AUTOVERIFY, 'Verify &automatic claims\tF6','TODO',
self.OnCheck)
])
self.CreateMenu(menuBar, '&Help',
[(wx.ID_ABOUT, '&About', 'Information about this program',
self.OnAbout) ])
self.SetMenuBar(menuBar) # Add the menuBar to the Frame
def SetTitle(self):
# MainWindow.SetTitle overrides wx.Frame.SetTitle, so we have to
# call it using super:
super(MainWindow, self).SetTitle('Scyther-gui: %s'%self.filename)
# Helper methods:
def defaultFileDialogOptions(self):
''' Return a dictionary with file dialog options that can be
used in both the save file dialog as well as in the open
file dialog. '''
return dict(message='Choose a file', defaultDir=self.dirname,
wildcard='*.spdl')
def askUserForFilename(self, **dialogOptions):
dialog = wx.FileDialog(self, **dialogOptions)
if dialog.ShowModal() == wx.ID_OK:
userProvidedFilename = True
self.filename = dialog.GetFilename()
self.dirname = dialog.GetDirectory()
self.SetTitle() # Update the window title with the new filename
else:
userProvidedFilename = False
dialog.Destroy()
return userProvidedFilename
# Event handlers:
def OnAbout(self, event):
msg = "Scyther GUI\n\nScyther and Scyther GUI\ndeveloped by Cas Cremers"
dialog = wx.MessageDialog(self,msg, 'About scyther-gui', wx.OK)
dialog.ShowModal()
dialog.Destroy()
def OnExit(self, event):
self.Close() # Close the main window.
def OnSave(self, event):
textfile = open(os.path.join(self.dirname, self.filename), 'w')
textfile.write(self.control.GetValue())
textfile.close()
def OnOpen(self, event):
if self.askUserForFilename(style=wx.OPEN,
**self.defaultFileDialogOptions()):
textfile = open(os.path.join(self.dirname, self.filename), 'r')
self.control.SetValue(textfile.read())
textfile.close()
def OnSaveAs(self, event):
if self.askUserForFilename(defaultFile=self.filename, style=wx.SAVE,
**self.defaultFileDialogOptions()):
self.OnSave(event)
os.chdir(self.dirname)
def RunScyther(self, mode):
Scytherthread.RunScyther(self,mode)
def OnVerify(self, event):
self.RunScyther("verify")
def OnAutoVerify(self, event):
self.RunScyther("autoverify")
def OnStatespace(self, event):
self.RunScyther("statespace")
def OnCheck(self, event):
self.RunScyther("check")
#---------------------------------------------------------------------------
class SettingsWindow(wx.Panel):
def __init__(self,parent,daddy):
wx.Panel.__init__(self,parent,-1)
self.win = daddy
# Bound on the number of runs
self.maxruns = int(Preference.get('maxruns','5'))
r1 = wx.StaticText(self,-1,"Maximum number of runs (0 disables bound)")
l1 = wx.SpinCtrl(self, -1, "",size=(150,-1))
l1.SetRange(0,100)
l1.SetValue(self.maxruns)
self.Bind(wx.EVT_SPINCTRL,self.EvtRuns,l1)
# Matchin options
self.match = int(Preference.get('match','0'))
claimoptions = ['typed matching','find basic type flaws','find all type flaws']
r2 = wx.StaticText(self,-1,"Matching type")
l2 = wx.RadioBox(self, -1, "",
wx.DefaultPosition,wx.DefaultSize,claimoptions,1,wx.RA_SPECIFY_COLS)
l2.SetSelection(self.match)
self.Bind(wx.EVT_RADIOBOX,self.EvtMatch,l2)
### MISC expert stuff
self.misc = Preference.get('scytheroptions','')
r10 = wx.StaticText(self,-1,"Additional parameters for the Scyther tool")
l10 = wx.TextCtrl(self,-1,self.misc,size=(150,-1))
self.Bind(wx.EVT_TEXT,self.EvtMisc,l10)
# Combine
space = 10
sizer = wx.FlexGridSizer(cols=3, hgap=space,vgap=space)
sizer.AddMany([ l1,r1, (0,0),
l2,r2, (0,0),
l10,r10, (0,0),
])
self.SetSizer(sizer)
self.SetAutoLayout(True)
def EvtMatch(self,evt):
self.match = evt.GetInt()
def EvtRuns(self,evt):
self.maxruns = evt.GetInt()
def EvtMisc(self,evt):
self.misc = evt.GetString()
def ScytherArguments(self):
""" Note: constructed strings should have a space at the end to
correctly separate the options.
"""
tstr = ""
# Number of runs
tstr += "--max-runs=%s " % (str(self.maxruns))
# Matching type
tstr += "--match=%s " % (str(self.match))
# Verification type
if self.mode == "check":
tstr += "--check "
if self.mode == "autoverify":
tstr += "--auto-claims "
elif self.mode == "statespace":
tstr += "--state-space "
# Anything else?
if self.misc != "":
tstr += " " + self.misc + " "
return tstr
#---------------------------------------------------------------------------
class SummaryWindow(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
def __init__(self,parent,daddy):
wx.ListCtrl.__init__(self,parent,-1, style=wx.LC_REPORT
| wx.BORDER_NONE
| wx.LC_SORT_ASCENDING
| wx.LC_SINGLE_SEL
)
listmix.ListCtrlAutoWidthMixin.__init__(self)
self.win = daddy
self.currentItem = -1
self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
self.InsertColumn(0, "Protocol")
self.InsertColumn(1, "Role")
self.InsertColumn(2, "Claim")
self.InsertColumn(3, "Label")
self.InsertColumn(4, "Parameters")
self.InsertColumn(5, "Status")
self.InsertColumn(6, "Attacks")
self.InsertColumn(7, "Comments")
def update(self):
self.DeleteAllItems()
self.claimlist = self.win.claimlist
for key in range(0,len(self.claimlist)):
cl = self.claimlist[key]
index = self.InsertStringItem(sys.maxint,cl.protocol)
self.SetStringItem(index,1,cl.role)
self.SetStringItem(index,2,cl.claim)
self.SetStringItem(index,3,cl.label)
self.SetStringItem(index,4,cl.param)
self.SetStringItem(index,5,cl.status)
self.SetStringItem(index,6,str(cl.attackcount))
self.SetStringItem(index,7,cl.comments)
self.SetItemData(index,key)
if cl.status == "Fail":
# Failed :(
item = self.GetItem(key)
item.SetTextColour(wx.RED)
self.SetItem(item)
else:
# Okay! But with bound?
if cl.comments.find("bounds") == -1:
# No bounds, great :)
item = self.GetItem(key)
item.SetTextColour(wx.GREEN)
self.SetItem(item)
#for i in range(0,7):
# self.SetColumnWidth(i,wx.LIST_AUTOSIZE)
def OnItemSelected(self, event):
self.currentItem = event.m_itemIndex
cl = self.claimlist[self.currentItem]
if cl.attackcount > 0:
display = Attackwindow.AttackWindow(cl)
display.Show(1)
self.Refresh()
event.Skip()
#---------------------------------------------------------------------------
class ErrorWindow(wx.TextCtrl):
def __init__(self,parent,daddy):
wx.TextCtrl.__init__(self,parent,-1, style=wx.TE_MULTILINE)
self.win = daddy
def update(self,summary):
self.SetValue(summary)
#---------------------------------------------------------------------------

45
gui/Misc.py Normal file
View File

@ -0,0 +1,45 @@
#
# Misc.py
# Various helper functions
#---------------------------------------------------------------------------
""" Import externals """
import os.path
#---------------------------------------------------------------------------
def confirm(question):
answer = ''
while answer not in ('y','n'):
print question,
answer = raw_input().lower()
return answer == 'y'
def exists(func,list):
return len(filter(func,list)) > 0
def forall(func,list):
return len(filter(func,list)) == len(list)
def uniq(li):
result = []
for elem in li:
if (not elem in result):
result.append(elem)
return result
# Return a sorted copy of a list
def sorted(li):
result = li[:]
result.sort()
return result
# path
def mypath(file):
""" Construct a file path relative to the scyther-gui main directory
"""
basedir = os.path.dirname(__file__)
return os.path.join(basedir,file)

132
gui/Preference.py Normal file
View File

@ -0,0 +1,132 @@
#!/usr/bin/python
"""
Preferences window and logic for saving and loading such things.
Thus, some default things can be set here.
init loads stuff
save save the settings after some changes
set(k,v)
get(k)
Currently used:
match
maxruns
scyther
scytheroptions
"""
#---------------------------------------------------------------------------
""" Import externals """
import wx
import os.path
from time import localtime,strftime
#---------------------------------------------------------------------------
""" Import scyther-gui components """
#---------------------------------------------------------------------------
""" Globals """
""" Locations of preferences. The last one is supposedly writable. """
prefname = "scythergui-config"
preflocs = ['/usr/local/lib/scyther','~/.scyther']
#---------------------------------------------------------------------------
class Preferences(dict):
def parse(self,line):
line = line.strip()
""" Skip comments """
if not line.startswith("#"):
split = line.find("=")
if split != -1:
key = line[:split].strip()
data = line[(split+1):]
self[key] = data.decode("string_escape")
print "Read %s=%s" % (key,self[key])
def load(self,file=""):
if file == None:
self["test1"] = "Dit is met een ' en een \", en dan\nde eerste dinges"
self["test2"] = "En dit de tweede"
elif file == "":
"""
Test default locations
"""
for f in preflocs:
self.load(os.path.join(os.path.expanduser(f),prefname))
else:
"""
Read this file
"""
if os.path.isfile(file):
fp = open(file,"r")
for l in fp.readlines():
self.parse(l)
fp.close()
def show(self):
print "Preferences:"
for k in self.keys():
print "%s=%s" % (k, self[k])
def save(self):
print "Saving preferences"
prefpath = os.path.expanduser(preflocs[-1])
if not os.access(prefpath,os.W_OK):
os.makedirs(prefpath)
savename = os.path.join(prefpath,prefname)
fp = open(savename,"w")
fp.write("# Scyther-gui configuration file.\n#\n")
date = strftime("%c",localtime())
fp.write("# Last written on %s\n" % (date))
fp.write("# Do not edit - any changes will be overwritten by Scyther-gui\n\n")
l = list(self.keys())
l.sort()
for k in l:
fp.write("%s=%s\n" % (k, self[k].encode("string_escape")))
fp.close()
def init():
"""
Load the preferences from a file, if possible
"""
global prefs
prefs = Preferences()
prefs.load("")
def get(key,alt=None):
global prefs
if prefs.has_key(key):
return prefs[key]
else:
return alt
def set(key,value):
global prefs
prefs[key]=value
return
def save():
global prefs
prefs.save()

112
gui/Scyther.py Executable file
View File

@ -0,0 +1,112 @@
#!/usr/bin/python
#
# Scyther interface
#
#---------------------------------------------------------------------------
""" Import externals """
import os
import sys
import StringIO
import tempfile
#---------------------------------------------------------------------------
""" Import scyther components """
import XMLReader
from Misc import *
#---------------------------------------------------------------------------
class Scyther(object):
def __init__ ( self):
self.program = "scyther"
self.options = ""
self.spdl = None
self.claims = None
def setInput(self,spdl):
self.spdl = spdl
def setFile(self,filename):
self.spdl = ""
fp = open(filename,"r")
for l in fp.readlines():
self.spdl += l
fp.close()
def verify(self):
if self.spdl:
# Write spdl to temp file
fp = tempfile.NamedTemporaryFile()
fp.write(self.spdl)
fp.flush()
# Run Scyther on temp file
self.cmd = "%s %s --dot-output --xml-output --plain '%s'" % (self.program,self.options,fp.name)
# If we are on windows, we don't get stderr. Maybe we need a
# switch to enforce this.
if sys.platform.startswith('linux'):
cmdline = "%s 2>/dev/null" % (self.cmd)
else:
# Non-linux does not generate stderr anyway
cmdline = "%s" % (self.cmd)
result = os.popen(cmdline)
xmlinput = result.read()
fp.close()
xmlfile = StringIO.StringIO(xmlinput)
reader = XMLReader.XMLReader()
self.claims = reader.readXML(xmlfile)
# Cleanup
del(xmlinput)
del(xmlfile)
return self.claims
else:
# No input yet!
return ""
def __str__(self):
if self.claims:
s = ""
for cl in self.claims:
s += str(cl) + "\n"
return s
else:
return "Scyther has not been run yet."
def basictest():
# Some basic testing
if sys.platform.startswith('win'):
print "Dir test"
p = os.popen("dir")
print p.read()
print p.close()
confirm("See the dir?")
# Scyther
x = Scyther()
if sys.platform.startswith('win'):
x.program = "c:\\Scyther.exe"
if not os.path.isfile(x.program):
print "I can't find the Scyther executable %s" % (x.program)
x.setFile("ns3.spdl")
x.verify()
if __name__ == '__main__':
basictest()

97
gui/Scytherthread.py Normal file
View File

@ -0,0 +1,97 @@
#!/usr/bin/python
#---------------------------------------------------------------------------
""" Import externals """
import wx
import wx.lib.newevent
import os
import sys
import re
import threading
import StringIO
#---------------------------------------------------------------------------
""" Import scyther components """
import XMLReader
""" Import scyther-gui components """
import Tempfile
import Claim
import Preference
import Scyther
#---------------------------------------------------------------------------
""" Global declaration """
(UpdateAttackEvent, EVT_UPDATE_ATTACK) = wx.lib.newevent.NewEvent()
busy = threading.Semaphore()
#---------------------------------------------------------------------------
def ScytherPath():
""" Retrieve Scyther path, and maybe test whether is is valid? """
program = Preference.get('scyther','scyther')
program = "\\\\Roivas\\public\\scyther-gui\\Scyther.exe"
return program
class ScytherThread(threading.Thread):
# Override Thread's __init__ method to accept the parameters needed:
def __init__ ( self, win, spdl, details ):
self.win = win
self.spdl = spdl
self.details = details
self.claims = []
threading.Thread.__init__ ( self )
def run(self):
global busy
evt = UpdateAttackEvent(status="Running Scyther...")
wx.PostEvent(self.win, evt)
self.claimResults()
evt = UpdateAttackEvent(status="Done.")
wx.PostEvent(self.win, evt)
#self.win.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
busy.release()
def claimResults(self):
""" Convert spdl to result (using Scyther) """
scyther = Scyther.Scyther()
if sys.platform.startswith('win'):
scyther.program = "c:\\Scyther.exe"
if not os.path.isfile(scyther.program):
print "I can't find the Scyther executable %s" % (scyther.program)
scyther.setInput(self.spdl)
self.claims = scyther.verify()
self.summary = str(scyther)
self.win.errors.update(self.summary)
def RunScyther(win,mode):
global busy
if (busy.acquire(False)):
# start the thread
win.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
win.settings.mode = mode
t = ScytherThread(win,win.control.GetValue(),"")
t.start()
win.threads = [ t ]

41
gui/Tempfile.py Normal file
View File

@ -0,0 +1,41 @@
#!/usr/bin/python
#---------------------------------------------------------------------------
""" Import externals """
import os
import tempfile
import atexit
#---------------------------------------------------------------------------
""" Local thing (can be done in numerous nicer ways) """
tempfiles = []
#---------------------------------------------------------------------------
def tempremove(tuple):
(fd,fpname) = tuple
#os.close(fd)
os.remove(fpname)
def cleanupshop():
global tempfiles
for tuple in tempfiles:
tempremove(tuple)
def tempcleaned(post=""):
global tempfiles
tuple = tempfile.mkstemp(post,"scyther_")
tempfiles.append(tuple)
return tuple
def tempcleanearly(tuple):
global tempfiles
tempfiles.remove(tuple)
tempremove(tuple)
atexit.register(cleanupshop)

204
gui/Term.py Normal file
View File

@ -0,0 +1,204 @@
#
# Term
#
import Trace
from Misc import *
class InvalidTerm(TypeError):
"Exception used to indicate that a given term is invalid"
class Knowledge(object):
def __init__(self,attack):
self.attack = attack
self.knowledge = []
def getInverse(self,term):
for pair in self.attack.inverseKeys:
if term == pair[0]:
return pair[1]
if term == pair[1]:
return pair[0]
# Get the inverse key
def getInverseKey(self,term):
# First try to see if the entire term has an inverse
result = self.getInverse(term)
if result != None:
return result
# If it is an apply term, try to see if the function has an inverse
if isinstance(term,TermApply):
result = self.getInverse(term.function)
if result != None:
return TermApply(result,term.argument)
# No inverse found, so term is its own inverse
return term
# Add a term to the knowledge
def add(self,term):
if term == None:
return
added = False
for x in term.deriveTerms(self):
if not x in self.knowledge:
added = True
self.knowledge.append(x)
# Something new was added, maybe this can help us to decrypt a term
# that we could not decrypt before
if added:
for x in self.knowledge:
if isinstance(x,TermEncrypt):
self.add(x)
def canDerive(self,term):
# We can derive free variables, because we can even choose them
if isinstance(term,TermVariable) and term.isFree():
return True
# We can derive a term if it is in the knowledge
# or all terms required to construct it are in the knowledge
if exists(lambda x: x == term,self.knowledge):
return True
constructors = term.constructorTerms()
if len(constructors) == 1 and constructors[0] == term:
# This is a single term, there is no need to look at constructor
# terms as we have already looked at the complete term
return False
return forall(lambda x: self.canDerive(x),constructors)
# Knowledge is the initial knowledge and all messages in sends
def buildKnowledge(self):
self.knowledge = self.attack.initialKnowledge[:]
for run in self.attack.semiTrace.runs:
# Intruder actions do not add knowledge processing them
# is a waste of time
if run.intruder:
continue
for event in run:
if isinstance(event,Trace.EventSend):
self.add(event.message)
self.add(event.fr)
self.add(event.to)
class Term(object):
def __init__(self):
self.types = None
def __str__(self):
raise InvalidTerm
def constructorTerms(self):
raise InvalidTerm
def deriveTerms(self,knowledge):
raise InvalidTerm
# Two terms are equal when their string rep is equal
def __cmp__(self,other):
return cmp(str(self),str(other))
class TermConstant(Term):
def __init__(self, constant):
Term.__init__(self)
self.value = str(constant)
def deriveTerms(self,knowledge):
return [self]
def constructorTerms(self):
return [self]
def __str__(self):
return self.value
class TermEncrypt(Term):
def __init__(self, value, key):
Term.__init__(self)
self.value = value
self.key = key
def deriveTerms(self,knowledge):
# In order to unpack an encrypted term we have to have the inverse key
inverse = knowledge.getInverseKey(self.key)
if knowledge.canDerive(inverse):
return [self] + [self.value] + self.value.deriveTerms(knowledge)
else:
return [self]
def constructorTerms(self):
return [self.value,self.key]
def __str__(self):
return "{%s}%s" % (self.value, self.key)
class TermApply(Term):
def __init__(self, function, argument):
Term.__init__(self)
self.function = function
self.argument = argument
def constructorTerms(self):
return [self.function,self.argument]
def deriveTerms(self,knowledge):
return [self]
def __str__(self):
return "%s(%s)" % (self.function, self.argument)
class TermVariable(Term):
def __init__(self, name, value):
Term.__init__(self)
self.name = name
self.value = value
def isFree(self):
return self.value == None
def constructorTerms(self):
if self.value != None:
return [self.value]
else:
return [self.name]
def deriveTerms(self,knowledge):
if self.value != None:
return [self,self.value] + self.value.deriveTerms(knowledge)
else:
return [self,self.name]
def __str__(self):
if (self.value != None):
return str(self.value)
else:
return str(self.name)
class TermTuple(Term):
def __init__(self, op1, op2):
Term.__init__(self)
self.op1 = op1
self.op2 = op2
def __str__(self):
return "%s,%s" % (self.op1,self.op2)
def constructorTerms(self):
return [self.op1,self.op2]
def deriveTerms(self,knowledge):
return [self,self.op1,self.op2]+self.op1.deriveTerms(knowledge)+self.op2.deriveTerms(knowledge)
def __getitem__(self,index):
if index == 0:
return self.op1
elif index == 1:
return self.op2
else:
return self.op2.__getitem__(index-1)

309
gui/Trace.py Normal file
View File

@ -0,0 +1,309 @@
#
# Trace
#
from Misc import *
class InvalidAction(TypeError):
"Exception used to indicate that a given action is invalid"
class InvalidEvent(TypeError):
"Exception used to indicate that a given event is invalid"
class SemiTrace(object):
def __init__(self):
self.runs = []
def totalCount(self):
count = 0
for run in self.runs:
count += len(run.eventList)
return count
def sortActions(self,actionlist):
newlist = actionlist[:]
newlist.sort(lambda x,y: self.getOrder(x,y))
return newlist
def getEnabled(self,previous):
enabled = []
for run in self.runs:
for event in run:
if event in previous or event in enabled:
continue
prec = self.getPrecedingEvents(event,previous)
if len(prec) == 0:
enabled.append(event)
return enabled
# Returns run,index tuples for all connections
def getConnections(self,event,removeIntruder=False):
if not removeIntruder:
return event.follows
result = []
if event.run.intruder:
for before in event.getBefore():
result.extend(self.getConnections(before,removeIntruder))
for x in event.follows:
fol = self.getEvent(x)
# If this is an intruder action descend into it
if fol.run.intruder:
result.extend(self.getConnections(fol,removeIntruder))
else:
result.append(x)
return uniq(result)
# Return the minimum set of preceding events for a given event
# that is the events before this event in the same run and all
# actions required by the partional ordering
# If previous is non empty remove all events already in previous
def getPrecedingEvents(self,event,previous=[]):
# If it is cached return cached version
if event.preceding != None:
return filter(lambda x: x not in previous,event.preceding)
preceding = []
for prec in event.getBefore():
preceding.append(prec)
preceding.extend(self.getPrecedingEvents(prec))
for x in event.follows:
fol = self.getEvent(x)
preceding.append(fol)
preceding.extend(self.getPrecedingEvents(fol))
preceding = uniq(preceding)
event.preceding = preceding
preceding = filter(lambda x: x not in previous,preceding)
return preceding
# Returns -1 if the first event has to be before the second one
# +1 if the second event has to be before the first one
# 0 if there is no order defined on the two events
def getOrder(self,event1,event2):
if (event1 in self.getPrecedingEvents(event2)):
return -1
if (event2 in self.getPrecedingEvents(event1)):
return 1
return 0
# Get event by run id and index
def getEvent(self,idx):
(rid,index) = idx
for run in self.runs:
if run.id != rid:
continue
for event in run:
if event.index == index:
return event
raise InvalidEvent
# Get all claim events in the trace
def getClaims(self):
claims = []
for run in self.runs:
for event in run:
if isinstance(event,EventClaim):
claims.append(event)
return claims
# Returns a list of all initiation events in the semitrace
def getInitiations(self):
initiations = []
for run in self.runs:
# Initiations are runs of honest agents
if (run.intruder):
continue
# Which contain no reads before the first send
for action in run:
if (isinstance(action,EventRead)):
break
elif (isinstance(action,EventSend)):
initiations.append(action)
break
return initiations
# Get all runs performed by a specific agent
def getAgentRuns(self,agent):
result = []
for run in self.runs:
if run.getAgent() == agent:
result.append(run)
return result
# Return a list of all runs that are parallel with this run
def getParallelRuns(self,run):
parallel = []
first = run.getFirstAction()
# Process all events that are before the end of the run
for event in self.getPrecedingEvents(run.getLastAction()):
# Only count those we haven't found yet
if event.run in parallel or event.run == run:
continue
# If the event is also after the beginning of the run it is
# parallel
if self.getOrder(event,first) == 1:
parallel.append(event.run)
return parallel
def getRun(self,runid):
for run in self.runs:
if run.id == runid:
return run
return None
class ProtocolDescription(object):
def __init__(self,protocol):
self.protocol = protocol
self.roledescr = {}
# Find event by label
def findEvent(self,eventlabel,eventType=None):
for (role,descr) in self.roledescr.items():
for event in descr:
if event.label == eventlabel:
if eventType == None or isinstance(event,eventType):
return event
# Return all events that should have occured before the given event
# if the protocol is executed exactly as specified
# (i.e. all previous events in the same run and the preceding events
# of the matching sends of all reads)
def getPrecedingEvents(self,eventlabel,eventType=None):
event = self.findEvent(eventlabel,eventType)
if event.preceding != None:
return event.preceding
preceding = event.getBefore()+[event]
for prev in preceding:
# For this event and all events that are before it in the run
# description see if it is a read and if it is also add the
# precedinglabelset of the matching send
if (isinstance(prev,EventRead)):
match = self.findEvent(prev.label,EventSend)
if match:
preceding.extend(self.getPrecedingEvents(match.label,EventSend))
preceding = uniq(preceding)
event.preceding = preceding
return preceding
# Calculate the preceding labelset that is all read events
# that are in the precedingEvents of a certain event
def getPrecedingLabelSet(self,eventlabel):
events = self.getPrecedingEvents(eventlabel)
events = filter(lambda x: isinstance(x,EventRead),events)
return [x.label for x in events]
# Calculate the roles in preceding labelset that is all roles that
# that are in the precedingEvents of a certain event
def getPrecedingRoleSet(self,eventlabel):
events = self.getPrecedingEvents(eventlabel)
roles = uniq([x.run.role for x in events])
return roles
def __str__(self):
s = ''
for x in self.roledescr.values():
for e in x:
s += str(e) + "\n"
return s
class Run(object):
def __init__(self):
self.id = None
self.protocol = None
self.role = None
self.roleAgents = {}
self.eventList = []
self.intruder = False
self.attack = None
def __iter__(self):
return iter(self.eventList)
def getAgent(self):
if self.intruder:
return None
return self.roleAgents[self.role]
def getFirstAction(self):
return self.eventList[0]
def getLastAction(self):
return self.eventList[-1]
class Event(object):
def __init__(self,index,label,follows):
self.index = index
self.label = label
self.follows = follows
self.run = None
self.preceding = None
self.rank = None
def shortLabel(self):
try:
return self.label[len(self.label)-1]
except:
return str(self.label)
def getBefore(self):
result = []
for event in self.run:
if (event == self):
return result
result.append(event)
# This should never happen
assert(False)
class EventSend(Event):
def __init__(self,index,label,follows,fr,to,message):
Event.__init__(self,index,label,follows)
self.fr = fr
self.to = to
self.message = message
def __str__(self):
if self.run.intruder:
return "SEND(%s)" % self.message
else:
return "SEND_%s(%s,%s)" % (self.shortLabel(),self.to,self.message)
class EventRead(Event):
def __init__(self,index,label,follows,fr,to,message):
Event.__init__(self,index,label,follows)
self.fr = fr
self.to = to
self.message = message
def __str__(self):
if self.run.intruder:
return "READ(%s)" % self.message
else:
return "READ_%s(%s,%s)" % (self.shortLabel(),self.fr, self.message)
class EventClaim(Event):
def __init__(self,index,label,follows,role,type,argument):
Event.__init__(self,index,label,follows)
self.role = role
self.type = type
self.argument = argument
self.broken = None
# A Claim should be ignored if there is an untrusted agent in the role
# agents
def ignore(self):
for untrusted in self.run.attack.untrusted:
if untrusted in self.run.roleAgents.values():
return True
return False
# Return (protocol,role)
def protocolRole(self):
return "(%s,%s)" % (self.run.protocol,self.run.role)
def argstr(self):
if self.argument == None:
return '*'
else:
return str(self.argument)
def __str__(self):
return "CLAIM_%s(%s, %s)" % (self.shortLabel(),self.type,self.argstr())

301
gui/XMLReader.py Normal file
View File

@ -0,0 +1,301 @@
#
# XMLReader
#
# Note:
# This requires python elementtree to work
# See: http://effbot.org/zone/element-index.htm
#
# On Fedora Core you can install this by installing the python-elementtree rpm
# Things will be a lot faster and consume less memory if you install the
# cElementTree module
#
# Check for cElementTree presence. Otherwise use ElementTree.
useiter = True
try:
import cElementTree
except ImportError:
useiter = False
from elementtree import ElementTree
## Simply pick cElementTree
#import cElementTree
## Simply pick ElementTree
#useiter = False
#from elementtree import ElementTree
import Term
import Attack
import Trace
import Claim
import sys
class XMLReader(object):
def __init__ (self):
self.varlist = []
pass
def readXML(self, input):
# Use iter parse when possble so we can clear the attack after reading
# it in order to preserve memory (this requires cElementTree)
attackbuffer = []
claims = []
if useiter:
parser = cElementTree.iterparse(input)
else:
parser = ElementTree.parse(input).findall('*')
for elem in parser:
# The iter parser receives the input in tuples (event and element)
# we only need the event
if useiter:
elem = elem[1]
if elem.tag == 'state':
attack = self.readAttack(elem)
attackbuffer.append(attack)
if useiter:
elem.clear()
if elem.tag == 'claimstatus':
claim = self.readClaim(elem)
claim.attacks = attackbuffer
claims.append(claim)
# link to parent
for attack in claim.attacks:
attack.claim = claim
attackbuffer = []
if useiter:
elem.clear()
return claims
# Read a term from XML
def readTerm(self,xml):
# If xml is None the term should also be none
if xml == None:
return None
# If this is a term variable read it directly
if (xml.tag in ('tuple','const','apply','encrypt','var')):
return self.readSubTerm(xml)
# Otherwise read from it's first child
children = xml.getchildren()
assert(len(children) == 1)
return self.readSubTerm(children[0])
def readSubTerm(self, tag):
if tag.tag == 'tuple':
return Term.TermTuple(self.readTerm(tag.find('op1')),self.readTerm(tag.find('op2')))
elif tag.tag == 'const':
return Term.TermConstant(tag.text)
elif tag.tag == 'apply':
return Term.TermApply(self.readTerm(tag.find('function')),self.readTerm(tag.find('arg')))
elif tag.tag == 'encrypt':
return Term.TermEncrypt(self.readTerm(tag.find('op')),self.readTerm(tag.find('key')))
elif tag.tag == 'var':
name = Term.TermConstant(tag.get('name'))
# Instantiate this variable if possible (note this list is empty while reading
# the variables section of the XML file)
for inst in self.varlist:
if inst.name == name:
return inst
# If it is not instantiated in varlist, just return a variable with this name and no
# value
return Term.TermVariable(name,None)
else:
raise Term.InvalidTerm, "Invalid term type in XML: %s" % tag.tag
def readEvent(self,xml):
label = self.readTerm(xml.find('label'))
follows = xml.findall('follows')
followlist = []
for follow in follows:
follow = follow.find('after')
if follow == None:
# Ignore follow definitions that do not contain after
continue
follow = (int(follow.get('run')),int(follow.get('index')))
followlist.append(follow)
(etype,index) = (xml.get('type'),int(xml.get('index')))
if etype in ('send','read'):
fr = self.readTerm(xml.find('from'))
to = self.readTerm(xml.find('to'))
message = self.readTerm(xml.find('message'))
if (etype == 'send'):
return Trace.EventSend(index,label,followlist,fr,to,message)
else:
return Trace.EventRead(index,label,followlist,fr,to,message)
elif xml.get('type') == 'claim':
role = self.readTerm(xml.find('role'))
etype = self.readTerm(xml.find('type'))
argument = self.readTerm(xml.find('argument'))
# Freshness claims are implemented as Empty claims with
# (Fresh,Value) as arguments
try:
if etype == 'Empty' and argument[0] == 'Fresh':
etype = Term.TermConstant('Fresh')
argument = argument[1]
elif etype == 'Empty' and argument[0] == 'Compromised':
etype = Term.TermConstant('Compromised')
argument = argument[1]
except:
pass
return Trace.EventClaim(index,label,followlist,role,etype,argument)
else:
raise Trace.InvalidAction, "Invalid action in XML: %s" % (xml.get('type'))
def readRun(self,xml):
assert(xml.tag == 'run')
run = Trace.Run()
run.id = int(xml.find('runid').text)
# TODO why is protocol name a term??
run.protocol = str(self.readTerm(xml.find('protocol')))
run.intruder = xml.find('protocol').get('intruder') == 'true'
run.role = xml.find('rolename').text
for role in xml.find('roleagents'):
name = role.find('rolename').text
agent = self.readTerm(role.find('agent'))
run.roleAgents[name] = agent
for eventxml in xml.find('eventlist'):
action = self.readEvent(eventxml)
action.run = run
run.eventList.append(action)
return run
# Read protocol description for a certain role
def readRoleDescr(self,xml):
assert(xml.tag == 'role')
run = Trace.Run()
# We will need the last label later on to see if a
# run is complete
run.lastLabel = None
run.role = xml.find('rolename').text
for eventxml in xml.find('eventlist'):
action = self.readEvent(eventxml)
action.run = run
run.eventList.append(action)
run.lastLabel = action.label
return run
def readTypeList(self,xml):
result = []
vartypes = xml.find('type').find('termlist')
for vartype in vartypes:
# We will assume that types are simple strings
result.append(str(self.readTerm(vartype)))
return result
def readClaim(self, xml):
claim = Claim.Claim()
for event in xml.getchildren():
if event.tag == 'claimtype':
claim.claimtype = self.readTerm(event)
elif event.tag == 'label':
# We store the full protocol,label construct for
# consistency with the technical parts, so it is left to
# the __str__ of claim to select the right element
claim.label = self.readTerm(event)
elif event.tag == 'protocol':
claim.protocol = self.readTerm(event)
elif event.tag == 'role':
claim.role = self.readTerm(event)
elif event.tag == 'parameter':
claim.parameter = self.readTerm(event)
elif event.tag == 'failed':
claim.failed = int(event.text)
elif event.tag == 'count':
claim.count = int(event.text)
elif event.tag == 'states':
claim.states = int(event.text)
elif event.tag == 'complete':
claim.complete = True
elif event.tag == 'timebound':
claim.timebound = True
else:
print >>sys.stderr,"Warning unknown tag in claim: %s" % claim.tag
claim.analyze()
return claim
def readAttack(self, xml):
self.varlist = []
attack = Attack.Attack()
attack.id = int(xml.get('id'))
# A state contains 4 direct child nodes:
# broken, system, variables and semitrace
# optionally a fifth: dot
for event in xml.getchildren():
if event.tag == 'broken':
attack.broken.append((self.readTerm(event.find('claim')),
self.readTerm(event.find('label'))))
elif event.tag == 'system':
attack.match = int(event.find('match').text)
for term in event.find('commandline'):
if attack.commandline != '':
attack.commandline += ' '
attack.commandline += term.text
for term in event.find('untrusted').find('termlist'):
attack.untrusted.append(str(self.readTerm(term)))
for term in event.find('initialknowledge').find('termlist'):
attack.initialKnowledge.append(self.readTerm(term))
for keypair in event.find('inversekeys'):
inverse = []
for term in keypair:
inverse.append(self.readTerm(term))
assert(len(inverse) == 0 or len(inverse) == 2)
attack.inverseKeys.append(inverse)
# TODO why is protocol name a term??
for protocolxml in event.findall('protocol'):
protocol = str(self.readTerm(protocolxml.find('name')))
descr = Trace.ProtocolDescription(protocol)
attack.protocoldescr[protocol] = descr
for rolexml in protocolxml.findall('role'):
roledescr = self.readRoleDescr(rolexml)
descr.roledescr[roledescr.role] = roledescr
elif event.tag == 'semitrace':
for runxml in event:
run = self.readRun(runxml)
run.attack = attack
attack.semiTrace.runs.append(run)
elif event.tag == 'dot':
# Apparently Scyther already generated dot output,
# store
attack.scytherDot = event.text
elif event.tag == 'variables':
# Read the variables one by one
for varxml in event:
if varxml.get('typeflaw') == 'true':
attack.typeflaws = True
var = self.readTerm(varxml.find('name').find('term'))
var.types = self.readTypeList(varxml.find('name'))
substxml = varxml.find('substitution')
# Read substitution if present
if substxml != None:
subst = self.readTerm(substxml.find('term'))
subst.types = self.readTypeList(substxml)
newvar = Term.TermVariable(var.name,subst)
newvar.types = var.types
var = newvar
attack.variables.append(var)
# When all have been read set self.varlist so that when
# we read terms in the attacks they can be filled in using
# this list
self.varlist = attack.variables
else:
print >>sys.stderr,"Warning unknown tag in attack: %s" % event.tag
return attack

222
gui/images/scyther-icon.svg Normal file
View File

@ -0,0 +1,222 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="841.88977pt"
height="595.27557pt"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.43"
version="1.0"
sodipodi:docbase="/home/cas/bzr/scyther-gui/images"
sodipodi:docname="scyther-icon.svg"
inkscape:export-filename="/home/cas/svn/scyther/Design download page Scyther/scyther-icon16.png"
inkscape:export-xdpi="1.6494846"
inkscape:export-ydpi="1.6494846">
<defs
id="defs4">
<linearGradient
id="linearGradient11078">
<stop
id="stop11080"
offset="0"
style="stop-color:#000000;stop-opacity:0.57731956;" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="0.209216"
id="stop11084" />
<stop
id="stop11086"
offset="0.65205503"
style="stop-color:#000000;stop-opacity:0.2371134;" />
<stop
id="stop11082"
offset="1"
style="stop-color:#000000;stop-opacity:0.67010307;" />
</linearGradient>
<linearGradient
id="linearGradient11052">
<stop
id="stop11054"
offset="0"
style="stop-color:#000000;stop-opacity:1;" />
<stop
style="stop-color:#276816;stop-opacity:1;"
offset="0.25145975"
id="stop11060" />
<stop
id="stop11066"
offset="0.57149941"
style="stop-color:#000000;stop-opacity:1;" />
<stop
id="stop11062"
offset="0.57149941"
style="stop-color:#010400;stop-opacity:1;" />
<stop
style="stop-color:#002583;stop-opacity:1;"
offset="0.8028897"
id="stop11064" />
<stop
id="stop11056"
offset="1"
style="stop-color:#000000;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient11044">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop11046" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop11048" />
</linearGradient>
<linearGradient
id="linearGradient7532">
<stop
id="stop7534"
offset="0"
style="stop-color:#9e9e9e;stop-opacity:1;" />
<stop
id="stop7536"
offset="1"
style="stop-color:#9e9e9e;stop-opacity:0;" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient11052"
id="linearGradient5737"
x1="521.7384"
y1="589.8822"
x2="521.7384"
y2="174.88217"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient11044"
id="linearGradient8364"
gradientUnits="userSpaceOnUse"
x1="323.7384"
y1="278.88214"
x2="477.7384"
y2="322.88214" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="526.18109"
inkscape:cy="372.04724"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1280"
inkscape:window-height="953"
inkscape:window-x="0"
inkscape:window-y="0">
<sodipodi:guide
orientation="horizontal"
position="296.88141"
id="guide1366" />
<sodipodi:guide
orientation="horizontal"
position="571.88122"
id="guide8421" />
<sodipodi:guide
orientation="vertical"
position="206.87725"
id="guide8423" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Black base"
inkscape:groupmode="layer"
id="layer1"
style="display:inline"
sodipodi:insensitive="true">
<rect
style="opacity:1;fill:url(#linearGradient5737);fill-opacity:1;stroke:#000000;stroke-width:6.73799992;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect7546"
width="323.44028"
height="320.51846"
x="194.92915"
y="162.62292" />
</g>
<g
inkscape:groupmode="layer"
id="layer9"
inkscape:label="inner fade"
sodipodi:insensitive="true"
style="display:inline">
<rect
style="opacity:1;fill:url(#linearGradient8364);fill-opacity:1;stroke:#000000;stroke-width:6.73799992;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:inline"
id="rect7489"
width="323.44028"
height="320.51846"
x="194.92915"
y="162.62292" />
</g>
<g
inkscape:groupmode="layer"
id="layer7"
inkscape:label="blacker fades"
style="display:inline"
sodipodi:insensitive="true" />
<g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="white inner"
style="display:inline">
<path
style="font-size:48px;font-style:oblique;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:15.39999962;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:8.60000038;stroke-dasharray:none;stroke-opacity:1;display:inline;font-family:Luxi Serif"
d="M 439.625,186.5625 L 228.875,186.96875 L 229,447.625 L 298.46875,447.0625 C 330.87282,446.92031 358.52897,437.28122 380.09375,420.40625 C 401.65809,403.53126 412.43728,382.80494 412.4375,358.21875 C 412.43728,345.47874 408.75517,334.539 401.375,325.375 C 394.57101,316.65817 381.16868,305.92772 361.21875,293.1875 L 346,283.46875 C 335.8349,277.00229 328.44922,270.61422 323.78125,264.34375 C 307.04223,258.53812 294.42062,259.0563 268.875,259.90625 C 278.05518,239.8488 296.11407,242.38722 300.21875,237.3125 C 304.95019,231.46292 305.43096,234.13065 309.84375,229.25 C 321.81701,216.0073 334.34348,209.33336 346.96875,206.0625 C 353.47544,203.07236 360.58299,201.56871 368.3125,201.65625 C 397.62337,201.98826 427.75001,225.34375 427.75,225.34375 L 439.625,186.5625 z "
id="path7544"
sodipodi:nodetypes="ccccsscccccsscscc" />
</g>
<g
inkscape:groupmode="layer"
id="layer8"
inkscape:label="upper white"
style="display:inline">
<path
style="font-size:48px;font-style:oblique;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:8.89999962;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:8.60000038;stroke-dasharray:none;stroke-opacity:1;display:inline;font-family:Luxi Serif"
d="M 439.625,186.5625 L 228.875,186.96875 L 229,447.625 L 298.46875,447.0625 C 330.87282,446.92031 358.52897,437.28122 380.09375,420.40625 C 401.65809,403.53126 412.43728,382.80494 412.4375,358.21875 C 412.43728,345.47874 408.75517,334.539 401.375,325.375 C 394.57101,316.65817 381.16868,305.92772 361.21875,293.1875 L 346,283.46875 C 335.8349,277.00229 328.44922,270.61422 323.78125,264.34375 C 307.04223,258.53812 294.42062,259.0563 268.875,259.90625 C 278.05518,239.8488 296.11407,242.38722 300.21875,237.3125 C 304.95019,231.46292 305.43096,234.13065 309.84375,229.25 C 321.81701,216.0073 334.34348,209.33336 346.96875,206.0625 C 353.47544,203.07236 360.58299,201.56871 368.3125,201.65625 C 397.62337,201.98826 427.75001,225.34375 427.75,225.34375 L 439.625,186.5625 z "
id="path6613"
sodipodi:nodetypes="ccccsscccccsscscc" />
</g>
<g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="beak"
style="display:inline"
sodipodi:insensitive="true" />
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="cyther"
style="display:inline"
sodipodi:insensitive="true" />
</svg>

After

Width:  |  Height:  |  Size: 8.5 KiB

249
gui/images/scyther-logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

51
gui/ns3.spdl Normal file
View File

@ -0,0 +1,51 @@
/*
* Needham-Schroeder protocol
*/
// PKI infrastructure
const pk: Function;
secret sk: Function;
inversekeys (pk,sk);
// The protocol description
protocol ns3(I,R)
{
role I
{
const ni: Nonce;
var nr: Nonce;
send_1(I,R, {I,ni}pk(R) );
read_2(R,I, {ni,nr}pk(I) );
send_3(I,R, {nr}pk(R) );
claim_i1(I,Secret,ni);
claim_i2(I,Secret,nr);
claim_i3(I,Niagree);
claim_i4(I,Nisynch);
}
role R
{
var ni: Nonce;
const nr: Nonce;
read_1(I,R, {I,ni}pk(R) );
send_2(R,I, {ni,nr}pk(I) );
read_3(I,R, {nr}pk(R) );
claim_r1(R,Secret,ni);
claim_r2(R,Secret,nr);
claim_r3(R,Niagree);
claim_r4(R,Nisynch);
}
}
// An untrusted agent, with leaked information
const Eve: Agent;
untrusted Eve;
compromised sk(Eve);

51
gui/nsl3.spdl Normal file
View File

@ -0,0 +1,51 @@
/*
* Needham-Schroeder-Lowe protocol
*/
// PKI infrastructure
const pk: Function;
secret sk: Function;
inversekeys (pk,sk);
// The protocol description
protocol nsl3(I,R)
{
role I
{
const ni: Nonce;
var nr: Nonce;
send_1(I,R, {I,ni}pk(R) );
read_2(R,I, {ni,nr,R}pk(I) );
send_3(I,R, {nr}pk(R) );
claim_i1(I,Secret,ni);
claim_i2(I,Secret,nr);
claim_i3(I,Niagree);
claim_i4(I,Nisynch);
}
role R
{
var ni: Nonce;
const nr: Nonce;
read_1(I,R, {I,ni}pk(R) );
send_2(R,I, {ni,nr,R}pk(I) );
read_3(I,R, {nr}pk(R) );
claim_r1(R,Secret,ni);
claim_r2(R,Secret,nr);
claim_r3(R,Niagree);
claim_r4(R,Nisynch);
}
}
// An untrusted agent, with leaked information
const Eve: Agent;
untrusted Eve;
compromised sk(Eve);

BIN
gui/scyther-gui-16.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
gui/scyther-gui-32.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
gui/scyther-gui-64.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

77
gui/scyther-gui.py Executable file
View File

@ -0,0 +1,77 @@
#!/usr/bin/python
#---------------------------------------------------------------------------
""" Import externals """
import wx
#---------------------------------------------------------------------------
""" Import scyther-gui components """
import Preference
import Mainwindow
import Misc
#---------------------------------------------------------------------------
class MySplashScreen(wx.SplashScreen):
def __init__(self):
bmp = wx.Image(Misc.mypath("images/scyther-splash.png")).ConvertToBitmap()
wx.SplashScreen.__init__(self, bmp,
wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT,
5000, None, -1)
self.Bind(wx.EVT_CLOSE, self.OnClose)
self.fc = wx.FutureCall(2000, self.ShowMain)
def OnClose(self, evt):
# Make sure the default handler runs too so this window gets
# destroyed
evt.Skip()
self.Hide()
# if the timer is still running then go ahead and show the
# main frame now
if self.fc.IsRunning():
self.fc.Stop()
self.ShowMain()
def ShowMain(self):
if self.fc.IsRunning():
self.Raise()
class ScytherApp(wx.App):
def OnInit(self):
"""
Load preferences file
"""
Preference.init()
"""
Create and show the splash screen. It will then create and show
the main frame when it is time to do so.
"""
splash = MySplashScreen()
splash.Show()
""" Build up """
self.mainWindow = Mainwindow.MainWindow('scythergui-default.spdl')
self.SetTopWindow(self.mainWindow)
self.mainWindow.Show()
return True
def OnExit(self):
""" Tear down """
if __name__ == '__main__':
scythergui = ScytherApp()
scythergui.MainLoop()

View File

@ -0,0 +1,51 @@
/*
* Needham-Schroeder protocol
*/
// PKI infrastructure
const pk: Function;
secret sk: Function;
inversekeys (pk,sk);
// The protocol description
protocol ns3(I,R)
{
role I
{
const ni: Nonce;
var nr: Nonce;
send_1(I,R, {I,ni}pk(R) );
read_2(R,I, {ni,nr}pk(I) );
send_3(I,R, {nr}pk(R) );
claim_i1(I,Secret,ni);
claim_i2(I,Secret,nr);
claim_i3(I,Niagree);
claim_i4(I,Nisynch);
}
role R
{
var ni: Nonce;
const nr: Nonce;
read_1(I,R, {I,ni}pk(R) );
send_2(R,I, {ni,nr}pk(I) );
read_3(I,R, {nr}pk(R) );
claim_r1(R,Secret,ni);
claim_r2(R,Secret,nr);
claim_r3(R,Niagree);
claim_r4(R,Nisynch);
}
}
// An untrusted agent, with leaked information
const Eve: Agent;
untrusted Eve;
compromised sk(Eve);

9
gui/todo.txt Normal file
View File

@ -0,0 +1,9 @@
- Ideally we somehow color the correct/incorrect tags in the editor.
- Line numbering is needed for the editor window otherwise you cannot
interpret attacks.
- Bottom bar should have obvious 'view' buttons for attacks that provide
views, to guide the user.
- Preferences window.
- Save in local file on close.
- Scyther executable should be able to be set by means of preferences.
(Using a file select dialog)