Skip to content

Instantly share code, notes, and snippets.

@dloss
Last active February 6, 2018 17:51
Show Gist options
  • Save dloss/067a271ce026a76e4e4e37a8e3df0124 to your computer and use it in GitHub Desktop.
Save dloss/067a271ce026a76e4e4e37a8e3df0124 to your computer and use it in GitHub Desktop.
sshvis Webapp source code <http://sshvis.appspot.com>
application: sshvis-hrd
version: 15
runtime: python27
api_version: 1
threadsafe: no
handlers:
- url: .*
script: main.py
indexes:
# AUTOGENERATED
# This index.yaml is automatically updated whenever the dev_appserver
# detects that a new type of query is run. If you want to manage the
# index.yaml file manually, remove the above marker line (the line
# saying "# AUTOGENERATED"). If you want to manage some indexes
# manually, move them above the marker line. The index.yaml file is
# automatically uploaded to the admin console when you next deploy
# your application using appcfg.py.
#!/usr/bin/env python
"""
OpenSSH key fingerprint visualization web app
Copyright (c) 2008 Dirk Loss
Original SSH key fingerprint visualization
idea and C implementation by Alexander von Gernler.
Copyright (c) 2008 Alexander von Gernler. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
import cgi
import md5
import random
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
augmentation_strings = {"ssh-chars" : " .o+=*BOX@%&#/^SE",
"count" : " 123456789abcdeSE",
"bubble" : " .oO8@XY#abcdefSE",
"fences" : " ##############SE",
"ats" : " @@@@@@@@@@@@@@SE"}
def draw_ascii_field(left, right=None, size_x=17, size_y=9, augmentation_string=" .o+=*BOX@%&#/^SE"):
""" Return a nice ASCII visualization of one or two given fields """
length = len(augmentation_string) - 1
sep = " "*6
# Upper border
out = "+%s+" % ("-" * size_x)
if right is not None:
out += "%s+%s+" % (sep, "-"*size_x)
out += "\n"
# Content
for y in range(size_y):
out += '|'
for x in range(size_x):
out += augmentation_string[min(left[x][y], length)]
out += "|"
if right is not None:
out += sep
out += '|'
for x in range(size_x):
out += augmentation_string[min(right[x][y], length)]
out += "|"
out += "\n"
# lower border
out += "+%s+" % ("-" * size_x)
if right is not None:
out += "%s+%s+" % (sep, "-"*size_x)
return out
def draw_html_field(left, right=None, size_x=17, size_y=9, augmentation_string=" .o+=*BOX@%&#/^SE"):
""" Return a nice HTML visualization of one or two given fields """
colors = ["dddddd", "bbbbbb", "999999", "777777", "555555", "333333", "111111", "bbbb00",
"999900", "777700", "555500", "333300", "111100", "1100110", "f0f0f0", "669933", "ff6633"]
# Every value the same. Start and end marked
colors = ["dddddd"]
for i in range(14):
colors.append("CC9966")
colors.append("669933")
colors.append("ff6633")
#header = '<colgroup width="%(width)s" span="%(span)s"></colgroup>' \ % {'width' =
rows = ""
for y in range(size_y):
row = ""
for x in range(size_x):
value = left[x][y]
# row += '<td bgcolor="#%s">&nbsp;</td>' % colors[value]
row += '<td bgcolor="#%s"><pre>%s</pre></td>' % (colors[value], augmentation_string[value])
rows += '<tr>%s</tr>\n' % row
return """
<table border="0">
%s
</table> """ % rows
def visual_field(digest, size_x=17, size_y=9,
augmentation_string=" .o+=*BOX@%&#/^SE",
show_ends=True, bounce_type="bounce"):
""" Return the visualization for a binary digest """
length = len(augmentation_string) - 1
field = []
for x in range(size_x):
line = []
for y in range(size_y):
line.append(0)
field.append(line)
# initialize field
x = size_x / 2
y = size_y / 2
# process raw key
for c in digest:
# each byte conveys four 2-bit move commands
inp = ord(c)
for b in range(4):
# evaluate 2 bit, rest is shifted later
if inp & 0x1:
x += 1
else:
x -= 1
if inp & 0x2:
y += 1
else:
y -= 1
if bounce_type == "torus":
x = x % size_x
y = y % size_y
else:
# assure we are still in bounds
x = max(x, 0)
y = max(y, 0)
x = min(x, size_x - 1)
y = min(y, size_y - 1)
# augment the field
if field[x][y] < length - 2:
field[x][y] += 1
inp = inp >> 2
if show_ends:
# Mark starting point in the middle
field[size_x / 2][size_y / 2] = length - 1
# Mark end point
field[x][y] = length
return field
def hex_md5(msg):
m=md5.new()
m.update(msg)
d = m.hexdigest()
return ":".join([ d[i:i+2] for i in range(0, len(d), 2)])
def html_selectionbox(name, values, selected=None):
""" Return a HTML selection box, optionally with a given selection as default """
options = []
for val in values:
if val == selected:
options.append("<option selected>%s</option>" % val)
else:
options.append("<option>%s</option>" % val)
options = "\n".join(options)
html = '<select name="%s" size="1">%s</select>' % (name, options)
return html
OUTPUT_STYLES = ["ASCII", "HTML"]
SIZES = ["3 * 3", "4 * 4", "5 * 5", "10 * 5", "10 * 10", "17 * 9", "20 * 20", "30 * 30", "80 * 40"]
AUGMENTATION_TYPES = ["ssh-chars", "count", "bubble", "fences", "ats"]
END_TYPES = ["show S/E", "no S/E"]
BOUNCE_TYPES = ["bounce", "torus"]
def header(hash="fc:94:b0:c1:e5:b0:98:7c:58:43:99:76:97:ee:9f:b7",
size="17 * 9", output_style="ASCII", augmentation_type="ssh-chars",
end_type="show S/E", bounce_type="bounce", is_random=False):
""" Return page header with input form """
if is_random:
checked = " checked"
else:
checked = ""
next_random = hex_md5(str(random.randint(0, 2**128)))
out = """<html>
<body>
<h1> SSH Fingerprint Visualization </h1>
<p> Original <a href="http://www.undeadly.org/cgi?action=article&sid=20080615022750&mode=expanded&count=13">idea</a> and <a href="http://marc.info/?l=openbsd-cvs&m=121321826818823&w=2">C implementation</a> by <a href="http://pestilenz.org/~grunk/">Alexander von Gernler</a>. This web app by <a href="http://dirk-loss.de">Dirk Loss</a>.
</p>
<p>More info: <a href="http://dirk-loss.de/sshvis/drunken_bishop.pdf">The drunken bishop</a>: An analysis of the OpenSSH fingerprint visualization algorithm (<a href="http://dirk-loss.de/sshvis/drunken_bishop.pdf">PDF</a>)</p>
<form action="/" method="post">
Fingerprint: <input type="text" name="hash" size="47" value="%s">
<input type="submit" value="Generate Visualization">
<br>Parameters:
%s %s %s %s %s
<input type="checkbox" name="is_random" value="%s"%s>randomize
<br>
</form>""" % (hash,
html_selectionbox("size", SIZES, selected=size),
html_selectionbox("output_style", OUTPUT_STYLES, selected=output_style),
html_selectionbox("augmentation_type", AUGMENTATION_TYPES, selected=augmentation_type),
html_selectionbox("end_type", END_TYPES, selected=end_type),
html_selectionbox("bounce_type", BOUNCE_TYPES, selected=bounce_type),
next_random,
checked)
return out
class MainPage(webapp.RequestHandler):
def get(self):
""" Process HTTP GET request """
self.response.out.write(header())
self.response.out.write("""</body></html>""")
def post(self):
""" Process HTTP POST request """
hash = cgi.escape(self.request.get('hash')) #.encode("utf-8")
size = cgi.escape(self.request.get('size'))
output_style = cgi.escape(self.request.get('output_style'))
augmentation_type = cgi.escape(self.request.get('augmentation_type'))
try:
augmentation_string = augmentation_strings[augmentation_type]
except KeyError:
augmentation_string = augmentation_strings["ssh-chars"]
try:
size_x, sep, size_y = size.split()
size_x = int(size_x)
size_y = int(size_y)
except (ValueError, AttributeError):
size_x = 17
size_y = 9
end_type = cgi.escape(self.request.get('end_type'))
if end_type == "no S/E":
show_ends = False
else:
show_ends = True
bounce_type = cgi.escape(self.request.get('bounce_type'))
is_random = cgi.escape(self.request.get('is_random'))
if is_random:
hash = is_random
# Header
write = self.response.out.write # shortcut
write(header(hash, size, output_style, augmentation_type, end_type, bounce_type, is_random))
try:
hash = "".join(hash.replace(" ","").split(":")).decode("hex")
except (TypeError, ValueError):
write("""<p>Illegal input format. Fingerprint must be given as even-length hex string.
Colons may be left out. Spaces are ignored.</p>
<form action="/" method="post">
<input type="hidden" name="hash" value="%s">
<input type="hidden" name="size" value="%s">
<input type="hidden" name="output_style" value="%s">
<input type="hidden" name="augmentation_type" value="%s">
<input type="hidden" name="end_type" value="%s">
<input type="hidden" name="bounce_type" value="%s">
<input type="submit" value="Convert to MD5 hash">
</form>""" % (hex_md5(hash), size, output_style, augmentation_type, end_type, bounce_type))
else:
field = visual_field(hash, size_x, size_y, augmentation_string, show_ends, bounce_type)
if output_style == "HTML":
viz = draw_html_field(field, size_x=size_x, size_y=size_y, augmentation_string=augmentation_string)
write(viz)
else:
viz = draw_ascii_field(field, size_x=size_x, size_y=size_y, augmentation_string=augmentation_string)
write('<pre>')
write(viz)
write('</pre></body></html>')
def main():
application = webapp.WSGIApplication(
[('/', MainPage)],
debug=False)
run_wsgi_app(application)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment