Last active
February 6, 2018 17:51
-
-
Save dloss/067a271ce026a76e4e4e37a8e3df0124 to your computer and use it in GitHub Desktop.
sshvis Webapp source code <http://sshvis.appspot.com>
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
application: sshvis-hrd | |
version: 15 | |
runtime: python27 | |
api_version: 1 | |
threadsafe: no | |
handlers: | |
- url: .* | |
script: main.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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"> </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