Lots of changes:

- Santiago documentation.
- A very basic demo.
- Thoughts on tests.
This commit is contained in:
Nick Daly 2012-02-21 22:27:37 -06:00
parent 0995358df0
commit 5333d9828a
9 changed files with 1113 additions and 36 deletions

View File

@ -0,0 +1 @@
{ "plinth": { "james": [ "http://192.168.0.12:8080" ], }, }

View File

@ -0,0 +1,2 @@
{ "james": { "wiki": "wiki" },
"ian": { "web": "plinth" }, }

View File

@ -0,0 +1,2 @@
{ "plinth": "http://192.168.0.13:8080",
"wiki": "http://192.168.0.13/wiki", }

View File

@ -0,0 +1,5 @@
{ "me": "nick",
"socket_port": 8080,
"max_hops": 3,
"proxy_list": ("tor")
}

625
ugly_hacks/santiago.html Normal file
View File

@ -0,0 +1,625 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.7: http://docutils.sourceforge.net/" />
<title>Santiago</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 6253 2010-03-02 00:24:53Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: left }
/* div.align-center * { */
/* text-align: left } */
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block {
margin-left: 2em ;
margin-right: 2em }
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="santiago">
<h1 class="title">Santiago</h1>
<h2 class="subtitle" id="less-discoverable-discovery">Less Discoverable Discovery?</h2>
<!-- -*- mode: rst; fill-column: 80; mode: auto-fill; -*- -->
<div class="section" id="disclaimer">
<h1>Disclaimer</h1>
<p><strong>The following is an ugly hack. Beware!</strong></p>
</div>
<div class="section" id="santiago-s-map">
<h1>Santiago's Map</h1>
<p>Santiago manages service discovery between FreedomBoxen, coordinating
connections between arbitrary servers and services. In essence, A requests a
service from B, B replies with the service's location, and A uses that location
for the service.</p>
<ol class="arabic simple">
<li>A sends a signed (and encrypted?) message to B's Santiago, requesting
information, in the form of:<ul>
<li>Will <em>X</em> do <em>Y</em> for me?</li>
<li>Optional: Reply to my Santiago at <em>Z</em>.</li>
</ul>
</li>
<li>If B does not recognize A or does not trust A, it stays silent.</li>
<li>If B recognizes and trusts A, it replies with a signed (and encrypted?)
message to A's Santiago, giving the location of A's usable service. If no
service is available, it replies with a rejection.</li>
</ol>
<p>In a nutshell, that's the important part. There are additional details to
manage, but they're implied and built on the system above.</p>
<div class="section" id="our-cheats">
<h2>Our Cheats</h2>
<p>Right now, we're cheating. We start by pairing boxes, exchanging both
box-specific PGP keys and Tor Hidden Service IDs. This allows boxes to trust
and communicate with one another, regardless of any adverserial interference.
Or, rather, any adverserial interference will be obvious and ignorable.</p>
</div>
<div class="section" id="message-exchange">
<h2>Message Exchange</h2>
<p>The Santiago service is running on <em>B</em>, waiting for connections. When it
receives a request message, that message must be signed by a known and trusted
party. If it is acceptably signed (with a known, and valid ID), <em>B</em> will
reply to <em>A</em>'s Santiago with an acceptably signed message.</p>
<p>The contents of the request message are as follows:</p>
<ul class="simple">
<li>I am requesting service <em>X</em>.</li>
<li>I am requesting that the service be performed by <em>Y</em>.</li>
<li>Optional: Reply to this message at <em>Z</em>.</li>
</ul>
<p>The message is signed by <em>A</em>, and optionally encrypted (if the message is
proxied, it must contain a &quot;To&quot; header). If <em>A</em> includes a location stanza,
<em>B</em> MUST respect that location in its response and update that location for
its Santiago service from <em>A</em> going forward.</p>
<p>In this document, I elide the Santiago acknowledgements (because they add a lot
of unnecessary noise - we can assume communication failures are failures of
acknolwedgement receipt). But, after each message that needs a response, an
acceptably signed acknowledgement message is returned. Otherwise the sender
preferentially moves on to the recipient's next Santiago address after a
sufficient amount of time has passed. An example of this communication, with
these details specified, follows:</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field"><th class="field-name">B -&gt; A:</th><td class="field-body">I'll gladly serve <em>X</em> for you, at <em>Z</em>, my good fellow.</td>
</tr>
<tr class="field"><th class="field-name">A -&gt; B:</th><td class="field-body">(No response)</td>
</tr>
<tr class="field"><th class="field-name">B -&gt; A:</th><td class="field-body"><em>(using a different Santiago address)</em> I'm serving <em>X</em> for <em>A</em> at <em>Z</em>.</td>
</tr>
<tr class="field"><th class="field-name">A -&gt; B:</th><td class="field-body">(Acknowledgment)</td>
</tr>
</tbody>
</table>
</div>
<div class="section" id="storing-service-data-and-network-structure">
<h2>Storing Service Data and Network Structure</h2>
<p>How are these data stored, to prevent both A and B from having to dance the
Santiago for each and every request?</p>
<p>Each node contains two dictionaries/hash-tables listing (1) what they serve and
who they serve it to, and (2) what services they use, who from, and where that
service is located.</p>
<div class="section" id="what-i-host-and-serve">
<h3>What I Host and Serve</h3>
<p>I offer these services to others.</p>
<p>These data are stored as pair of dictionaries:</p>
<ul>
<li><p class="first">The GPG ID to Service dictionary. This lists which service each user is
authorized for:</p>
<pre class="literal-block">
0x0928: [ &quot;proxy&quot;: &quot;proxy&quot;, &quot;wiki&quot;: &quot;wiki&quot;,
&quot;drinking buddy&quot;: &quot;drinking buddy&quot; ]
0x7747: [ &quot;wiki&quot;: &quot;wiki&quot;, &quot;proxy&quot;: &quot;restricted_proxy&quot; ]
</pre>
</li>
<li><p class="first">The Service to Location dictionary. This lists the locations each service
runs on:</p>
<pre class="literal-block">
&quot;wiki&quot;: [ 192.168.1.1, &quot;superduperwiki.onion&quot; ]
&quot;proxy&quot;: [ 8.8.8.8 ]
&quot;restricted_proxy&quot;: [ 4.4.4.4 ]
</pre>
</li>
</ul>
</div>
<div class="section" id="others-services-i-know-of">
<h3>Others' Services I Know Of</h3>
<p>I consume these services, they are offered by others.</p>
<p>These data are stored as a triple-key dictionary, with the following mappings:</p>
<pre class="literal-block">
Service X: { GPG ID1: [ location, location, location ],
GPG ID2: [ location, location ], }
Service Y: { GPG ID2: [ location, location, location ],
GPG ID3: [ location, location ], }
</pre>
<p>This allows fast lookup from the service desired to the users that host the
service, to the actual locations that service is offered. This allows the user
to quickly decide which service provider to use and to try all locations
controlled by that service provider very quickly and easily.</p>
</div>
</div>
<div class="section" id="data-use">
<h2>Data Use</h2>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field"><th class="field-name">TODO:</th><td class="field-body">Revise to reduce communication to logical minimum number of connections,
exchanges, and communications.</td>
</tr>
</tbody>
</table>
<p>When <em>A</em> is connecting to <em>B</em>'s service, it will attempt to connect to that
service, which B will validate before permitting the connection. If the service
is non-responsive, <em>A</em> will query <em>B</em> for the service. If <em>B</em> is generally
non-responsive, <em>A</em> will move on to <em>C</em>. <em>A</em> will ask <em>C</em> for the service. If
<em>C</em> cannot provide the service, <em>A</em> will ask <em>C</em> to request the service from
<em>B</em>. If <em>C</em> can reach <em>B</em> and <em>B</em> authorizes <em>A</em>, <em>B</em> will respond
affirmatively to <em>A</em> with the service's location.</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field"><th class="field-name">A -&gt; B:</th><td class="field-body">(Connecting to Service!)</td>
</tr>
<tr class="field"><th class="field-name">B:</th><td class="field-body">(Validating Service and rejecting for some reason, e.x., A hasn't been
reauthorized for this service recently enough, and because it's Wednesday.)</td>
</tr>
<tr class="field"><th class="field-name">B -&gt; A:</th><td class="field-body">(No response)</td>
</tr>
<tr class="field"><th class="field-name">A -&gt; B:</th><td class="field-body">Will you serve X?</td>
</tr>
<tr class="field"><th class="field-name">B -&gt; A:</th><td class="field-body">(No response, A can't reach B's Santiago.)</td>
</tr>
<tr class="field"><th class="field-name">A -&gt; C:</th><td class="field-body">Will you serve X?</td>
</tr>
<tr class="field"><th class="field-name">C -&gt; A:</th><td class="field-body">No!</td>
</tr>
<tr class="field"><th class="field-name">A -&gt; C:</th><td class="field-body">Will B serve X?</td>
</tr>
<tr class="field"><th class="field-name">C -&gt; B:</th><td class="field-body">Will you serve X for A?</td>
</tr>
<tr class="field"><th class="field-name">B -&gt; A:</th><td class="field-body">Hey, buddy, here's X!</td>
</tr>
</tbody>
</table>
</div>
<div class="section" id="proxied-service-requesting">
<h2>Proxied service requesting</h2>
<div class="section" id="the-simple-case">
<h3>The Simple Case</h3>
<p>I'm looking for somebody to provide a service, <em>X</em>.</p>
<p><em>A</em> sends a request to <em>C</em>, and <em>C</em> doesn't respond. <em>A</em> requests the
service from <em>B</em> and <em>B</em> NBKs. <em>A</em> requests that <em>B</em> proxy my request
to <em>C</em>, in case <em>B</em> can reach <em>C</em>. <em>C</em> replies directly to <em>A</em>, and
we begin communicating on that service:</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field"><th class="field-name">A -&gt; C:</th><td class="field-body">Will you serve X?</td>
</tr>
<tr class="field"><th class="field-name">C -&gt; A:</th><td class="field-body">(No response)</td>
</tr>
<tr class="field"><th class="field-name">A -&gt; B:</th><td class="field-body">Will you serve X?</td>
</tr>
<tr class="field"><th class="field-name">B -&gt; A:</th><td class="field-body">No!</td>
</tr>
<tr class="field"><th class="field-name">A -&gt; B:</th><td class="field-body">Will C serve X?</td>
</tr>
<tr class="field"><th class="field-name">B -&gt; C:</th><td class="field-body">Will you serve X for A?</td>
</tr>
<tr class="field"><th class="field-name">C -&gt; A:</th><td class="field-body">Hey, buddy, here's X! Let's go out for beer later.</td>
</tr>
</tbody>
</table>
</div>
<div class="section" id="more-complicated-cases">
<h3>More Complicated Cases</h3>
<p>I know <em>D</em> offers a service, <em>X</em>, but I can't get in touch with it.</p>
<p><em>A</em> requests <em>X</em> from <em>D</em>, and <em>D</em> never responds. <em>A</em> requests that <em>B</em> find
<em>D</em>. <em>B</em> doesn't know <em>D</em> and forwards the request to a friend <em>C</em>. <em>C</em> knows
<em>D</em> and sends the message along. <em>D</em> tries to respond directly to <em>A</em>, but
can't, so it sends replies back up the chain.</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field"><th class="field-name">A -&gt; D:</th><td class="field-body">Will you serve X?</td>
</tr>
<tr class="field"><th class="field-name">D -&gt; A:</th><td class="field-body">(No response)</td>
</tr>
<tr class="field"><th class="field-name">A -&gt; B:</th><td class="field-body">Will D serve X for me?</td>
</tr>
<tr class="field"><th class="field-name">B -&gt; C:</th><td class="field-body">Will D serve X for A?</td>
</tr>
<tr class="field"><th class="field-name">C -&gt; D:</th><td class="field-body">Will you serve X for A?</td>
</tr>
<tr class="field"><th class="field-name">D -&gt; A:</th><td class="field-body">Hey, buddy, here's X!</td>
</tr>
<tr class="field"><th class="field-name">A -&gt; D:</th><td class="field-body">(No response)</td>
</tr>
<tr class="field"><th class="field-name">D -&gt; C:</th><td class="field-body">I'm serving X for A.</td>
</tr>
<tr class="field"><th class="field-name">C -&gt; B:</th><td class="field-body">D's serving X for A.</td>
</tr>
<tr class="field"><th class="field-name">B -&gt; A:</th><td class="field-body">D's serving X for you.</td>
</tr>
</tbody>
</table>
<p>Each message is signed, but only the first message (A's message) is inviolable.
Each client then passes the message, stripping off intermediary signatures, and
then signing the message for each of its friends.</p>
<p>A message looks like:</p>
<pre class="literal-block">
---- A's Signed Message Starts Here ----
To: D's GPG key.
---- D's Encrypted Message Starts Here ----
Hey *D*, will you serve *X* for me?
Please reply to 5.onion.
---- D's Encrypted Message Ends Here ----
---- A's Signed Message Ends Here ----
</pre>
<p>A forwarded message, from B to C, looks like:</p>
<pre class="literal-block">
---- B's Signed Message Starts Here ----
---- A's Signed Message Starts Here ----
To: D's GPG key.
---- D's Encrypted Message Starts Here ----
Hey *D*, will you serve *X* for me?
Please reply to 5.onion.
---- D's Encrypted Message Ends Here ----
---- A's Signed Message Ends Here ----
---- B's Signed Message Ends Here ----
</pre>
<p>When forwarded over again, from C to D, it looks like:</p>
<pre class="literal-block">
---- C's Signed Message Starts Here ----
---- A's Signed Message Starts Here ----
To: D's GPG key.
---- D's Encrypted Message Starts Here ----
Hey *D*, will you serve *X* for me?
Please reply to 5.onion.
---- D's Encrypted Message Ends Here ----
---- A's Signed Message Ends Here ----
---- C's Signed Message Ends Here ----
</pre>
</div>
</div>
</div>
<div class="section" id="unit-tests">
<h1>Unit Tests</h1>
<p>These buggers are neat. We need to fake known and pre-determined communications
to verify the servers and clients are correctly and independently responding
according to the protocol.</p>
</div>
<div class="section" id="attacks">
<h1>Attacks</h1>
<p>Of <em>course</em> this is vulnerable. It's on the internet, isn't it?</p>
<div class="section" id="discovery">
<h2>Discovery</h2>
<p>A discovered box is shut down or compromised. It can lie to its requestors and
not perform its functions. It can also allow connections and expose
connecting clients. If the client is compromisable (within reach), it also can
be compromised. We can try, but every service that isn't run directly over Tor
identifies one user to another.</p>
</div>
<div class="section" id="deception">
<h2>Deception</h2>
<p>This is probably the largest worry, where B fakes A's responses.</p>
</div>
</div>
<div class="section" id="mitigations">
<h1>Mitigations</h1>
<p>We gain a lot by relying on the WOT, and only direct links in the WOT. We also
gain a lot by requiring every communication to be signed (and maximally
encrypted).</p>
<p>Once a key is compromised, we're vulnerable, but to what exactly? What attacks
can an adversary who's compromised a secret key perform? As long as you don't
have any publicly identifiable (or public-facing) services in your Santiago,
then not much. If you're identifiable by your Santiago, and you've permitted
the attacker to see an identifiable service (including your Santiago instances),
that service and all co-located services could be shut down. If the service
identifies you (and not just your box), you're vulnerable. An attacker will
shortly identify all the services you've given it access to.</p>
<p>An attacker can try to identify your friends, though will have trouble if you
send your proxied requests with non-public methods, or you don't proxy at all.</p>
</div>
<div class="section" id="outstanding-questions">
<h1>Outstanding Questions</h1>
</div>
</div>
</body>
</html>

260
ugly_hacks/santiago.py Normal file
View File

@ -0,0 +1,260 @@
#! /usr/bin/python # -*- fill-column: 80 -*-
"""The Santiago service.
It runs on a port (the Port of Santiago) with sending and receiving services
(the Santiagi) with a simple authentication mechanism ("the Dance of the
Santiagi" or "Santiago's Dance"). The idea is that systems can share
identification information without ever revealing their location (where in the
world is Santiago?).
This *is* signed identity statements, just on a
service-by-service-for-individual-people level. Granularity, bitches!
hackyhackyhacky hacks, but the right structure sans details (a tracer-bullet
structure, see "The Pragmatic Programmer", Hunt & Thomas, 2000, pg. 48). If
you have a copy of TPP around, also see pgs: 111 120 161 186 192 238 248 258.
This file is distributed under the GPL, version 3 or later, at your discretion.
Santiago, he
smiles like a Buddah, 'neath
his red sombrero.
"""
# If you're crazy like me, you can turn this script into its own
# documentation by running::
#
# python pylit.py -c backup - | rst2html > backup.html
#
# You'll need PyLit_ and ReStructuredText_ for this to work correctly.
#
# .. _pylit: http://pylit.berlios.de/
# .. _restructuredtext: http://docutils.sourceforge.net/rst.html
#
# .. contents::
import cherrypy
import os
from simplejson import JSONEncoder
DEBUG = 0
encoder = JSONEncoder()
if DEBUG:
for x in range(0, 3):
for y in range(0, 7):
print "WARNING",
print ""
print "You're in DEBUG MODE! You are surprisingly vulnerable! Raar!"
def fix_old_cherrypy():
"""Make Lenny's CherryPy forward-compatible."""
for x in range(0,3):
for y in range(0, 7):
print "WARNING",
print ""
print("You're using an old CherryPy version! We're faking it!")
print("Expect the unexpected! Raar!")
def jsonify_tool_callback(*args, **kwargs):
response = cherrypy.response
response.headers['Content-Type'] = 'application/json'
response.body = encoder.iterencode(response.body)
cherrypy.tools.json_out = cherrypy.Tool('before_finalize', jsonify_tool_callback, priority=30)
if cherrypy.__version__ < "3.2":
fix_old_cherrypy()
class Santiago(object):
"""Santiago's base class, containing listener and sender defaults."""
DEFAULT_RESPONSE = """\
<html><head><title>Use it right.</title></head><body>
<p>Nice try, now try again with a request like:</p>
<p>http://localhost:8080/santiago/(gpgId)/(service)/(server)</p>
<dl>
<dt>gpgId</dt><dd>james, ian</dd>
<dt>service</dt><dd>wiki, web</dd>
<dt>server</dt><dd>nick</dd>
</dl>
<p>This'll get you good results:</p>
<code><a href="http://localhost:8080/santiago/james/wiki/nick">
http://localhost:8080/santiago/james/wiki/nick</a></code>
<p>See the <code>serving_to</code>, <code>serving_what</code>, and
<code>me</code> variables.</p>
</body></html>"""
def __init__(self, instance):
self.load_instance(instance)
# TODO Does the listener need to know what services others are running?
# TODO Does the sender need to know what services I'm running?
self.load_serving_to()
self.load_serving_what()
self.load_known_services()
def am_i(self, server):
return str(self.me) == str(server)
def load_instance(self, instance):
"""Load instance settings from a file.
A terrible, unforgivable hack. But it's a pretty effective prototype,
allowing us to add and remove attributes really easily.
"""
settings = run_file("%s%ssettings.py" % (instance, os.sep))
for key, value in settings.iteritems():
setattr(self, key, value)
self.instance = instance
# FIXME I need to handle instance vs controller correctly. this is the
# wrong place.
#
# I'm putting instance data into the controller, which is nutters. Too
# tired to fix tonight, though.
#
# These data should probably be loaded in the server and listener,
# respectively, but I don't know whether one needs to know about the other's
# services.
def load_serving_to(self):
"""Who can see which of my services?"""
self.serving_to = run_file("%s%sserving_to.py" % (self.instance,
os.sep))
def load_serving_what(self):
"""What location does that service translate to?"""
self.serving_what = run_file("%s%sserving_what.py" % (self.instance,
os.sep))
def load_known_services(self):
"""What services do I know of?"""
self.known_services = run_file("%s%sknown_services.py" % (self.instance,
os.sep))
@cherrypy.expose
def index(self):
"""Do nothing, unless we're debugging."""
if DEBUG:
return DEFAULT_RESPONSE
class SantiagoListener(Santiago):
"""Listens for requests on the santiago port."""
def __init__(self, instance, port=8080):
super(SantiagoListener, self).__init__(instance=instance)
self.socket_port = port
@cherrypy.expose
@cherrypy.tools.json_out()
def santiago(self, from_id=None, to_id=None, service=None, hops=0): #, new_santiago_id=""):
"""Handles an incoming request.
FIXME: handle new Santiago ID list.
"""
message = { "requester": from_id,
"server": to_id,
"service": service,
"hops": hops, }
#"santiago": new_santiago_id }
# FIXME: this is being dumb and not working how I expect it. later.
# if not self.i_am(message["server"]):
# return self.proxy_santiago_request(message)
try:
return self.serving_what[
self.serving_to[message["requester"]][message["service"]]]
except KeyError:
# TODO: handle responses. should a fail just timeout?
return None
@cherrypy.tools.json_out()
def proxy_santiago_request(self, message, hops=3):
"""Passes a Santiago request off to another known host.
We're trying to search the friend list for the target server.
"""
# handle crap input.
if (hops > self.max_hops):
hops = self.max_hops
if (hops < 1):
return
# this counts as a hop.
hops -= 1
# TODO pull this off, another day.
return str(message)
class SantiagoSender(Santiago):
"""Sendss the Santiago request to a Santiago service."""
def __init__(self, instance, proxy):
super(SantiagoSender, self).__init__(instance=instance)
self.proxy = proxy if proxy in self.proxy_list else None
def request(self, destination, resource):
"""Sends a request for a resource to a known Santiago.
The request MUST include the following:
- A service.
- A server.
The request MAY include the following:
- Other Santiago listeners.
- An action.
post:
not (__return__["destination"] is None)
not (__return__["service"] is None)
# TODO my request is signed with my GPG key, recipient encrypted.
"""
pass # TODO: do.
# utility functions
# =================
def run_file(filename):
"""Returns the result of executing the Python file. Terrible idea. Effective
hack.
TODO: Replace this with James's database stuff!
If you try to use this in the wild, I will hunt you down and cut you.
"""
with open(filename) as in_file:
return eval("".join(in_file.readlines()))
if __name__ == "__main__":
santiago = SantiagoListener("fbx")
cherrypy.quickstart(santiago)

View File

@ -24,31 +24,56 @@ for the service.
information, in the form of:
- Will *X* do *Y* for me?
- (By the way I'm at *Z*)
- Optional: Reply to my Santiago at *Z*.
#. If B does not recognize A or does not trust A, it stays silent.
#. If B recognizes and trusts A, it replies with a signed (and encrypted?)
message to A's Santiago, giving the location of A's usable service. If no
service is available, it reples with a rejection.
service is available, it replies with a rejection.
In a nutshell, that's the important part. There are additional details to
manage, but they're implied and build on the system above.
manage, but they're implied and built on the system above.
Our Cheats
----------
Right now, we're cheating. We start by pairing boxes, exchanging both
box-specific PGP keys and Tor Hidden Service IDs. This allows boxes to trust
and communicate with one another, regardless of any adverserial interference.
Or, rather, any adverserial interference will be obvious and ignorable.
Message Exchange
----------------
The Santiago service is running on ``B``, waiting for connections. When it
The Santiago service is running on *B*, waiting for connections. When it
receives a request message, that message must be signed by a known and trusted
party. If it is acceptably signed (with a known, and valid ID), ``B`` will
reply to ``A``'s Santiago with an acceptably signed message.
party. If it is acceptably signed (with a known, and valid ID), *B* will
reply to *A*'s Santiago with an acceptably signed message.
The contents of the request message are as follows:
- I am requesting service *X*.
- I am requesting that the service be performed by *Y*.
- Optional: I am located at *Z*.
- Optional: Reply to this message at *Z*.
The message is signed by ``A``, and optionally encrypted (if the message is
proxied, it must contain a "To" header).
The message is signed by *A*, and optionally encrypted (if the message is
proxied, it must contain a "To" header). If *A* includes a location stanza,
*B* MUST respect that location in its response and update that location for
its Santiago service from *A* going forward.
In this document, I elide the Santiago acknowledgements (because they add a lot
of unnecessary noise - we can assume communication failures are failures of
acknolwedgement receipt). But, after each message that needs a response, an
acceptably signed acknowledgement message is returned. Otherwise the sender
preferentially moves on to the recipient's next Santiago address after a
sufficient amount of time has passed. An example of this communication, with
these details specified, follows:
:B -> A: I'll gladly serve *X* for you, at *Z*, my good fellow.
:A -> B: (No response)
:B -> A: *(using a different Santiago address)* I'm serving *X* for *A* at *Z*.
:A -> B: (Acknowledgment)
Storing Service Data and Network Structure
------------------------------------------
@ -88,8 +113,10 @@ I consume these services, they are offered by others.
These data are stored as a triple-key dictionary, with the following mappings::
Service -> [ GPG, ID ]
GPG ID -> [ Location, location, location ]
Service X: { GPG ID1: [ location, location, location ],
GPG ID2: [ location, location ], }
Service Y: { GPG ID2: [ location, location, location ],
GPG ID3: [ location, location ], }
This allows fast lookup from the service desired to the users that host the
service, to the actual locations that service is offered. This allows the user
@ -102,36 +129,111 @@ Data Use
:TODO: Revise to reduce communication to logical minimum number of connections,
exchanges, and communications.
When ``A`` is connecting to ``B``'s service, it will attempt to connect to that
service (and verify ``B``'s identity with a signed handshake before using the
service?). If the service is non-responsive, ``A`` will query ``B`` for the
service. If ``B`` is generally non-responsive, ``A`` will move on to ``C``.
``A`` will ask ``C`` for the service. If ``C`` cannot provide the service,
``A`` will ask ``C`` to request the service from ``B``. If ``C`` can reach
``B`` and ``B`` authorizes ``A``, ``B`` will respond affirmatively to ``A`` with
the service's location.
When *A* is connecting to *B*'s service, it will attempt to connect to that
service, which B will validate before permitting the connection. If the service
is non-responsive, *A* will query *B* for the service. If *B* is generally
non-responsive, *A* will move on to *C*. *A* will ask *C* for the service. If
*C* cannot provide the service, *A* will ask *C* to request the service from
*B*. If *C* can reach *B* and *B* authorizes *A*, *B* will respond
affirmatively to *A* with the service's location.
Our Cheats
----------
Right now, we're cheating. We start by pairing boxes, exchanging both
box-specific PGP keys and Tor Hidden Service IDs. This allows boxes to trust
and communicate with one another, regardless of any adverserial interference.
Or, rather, any adverserial interference will be obvious and ignorable.
Next Steps
==========
:A -> B: (Connecting to Service!)
:B: (Validating Service and rejecting for some reason, e.x., A hasn't been
reauthorized for this service recently enough, and because it's Wednesday.)
:B -> A: (No response)
:A -> B: Will you serve X?
:B -> A: (No response, A can't reach B's Santiago.)
:A -> C: Will you serve X?
:C -> A: No!
:A -> C: Will B serve X?
:C -> B: Will you serve X for A?
:B -> A: Hey, buddy, here's X!
Proxied service requesting
--------------------------
``Me`` sends a request to ``B``, and ``B`` doesn't respond. ``Me`` requests the
service from ``A`` and ``A`` NAKs. ``Me`` requests that ``A`` proxy my request
to ``B``, in case ``A`` can reach ``B``. ``B`` replies directly to ``Me``, and
we begin communicating on that service.
The Simple Case
~~~~~~~~~~~~~~~
:TODO: What is the maximum number of hops that we can securely achieve from this
system while keeping intermedia hops secret? One? More?
I'm looking for somebody to provide a service, *X*.
*A* sends a request to *C*, and *C* doesn't respond. *A* requests the
service from *B* and *B* NBKs. *A* requests that *B* proxy my request
to *C*, in case *B* can reach *C*. *C* replies directly to *A*, and
we begin communicating on that service:
:A -> C: Will you serve X?
:C -> A: (No response)
:A -> B: Will you serve X?
:B -> A: No!
:A -> B: Will C serve X?
:B -> C: Will you serve X for A?
:C -> A: Hey, buddy, here's X! Let's go out for beer later.
More Complicated Cases
~~~~~~~~~~~~~~~~~~~~~~
I know *D* offers a service, *X*, but I can't get in touch with it.
*A* requests *X* from *D*, and *D* never responds. *A* requests that *B* find
*D*. *B* doesn't know *D* and forwards the request to a friend *C*. *C* knows
*D* and sends the message along. *D* tries to respond directly to *A*, but
can't, so it sends replies back up the chain.
:A -> D: Will you serve X?
:D -> A: (No response)
:A -> B: Will D serve X for me?
:B -> C: Will D serve X for A?
:C -> D: Will you serve X for A?
:D -> A: Hey, buddy, here's X!
:A -> D: (No response)
:D -> C: I'm serving X for A.
:C -> B: D's serving X for A.
:B -> A: D's serving X for you.
Each message is signed, but only the first message (A's message) is inviolable.
Each client then passes the message, stripping off intermediary signatures, and
then signing the message for each of its friends.
A message looks like::
---- A's Signed Message Starts Here ----
To: D's GPG key.
---- D's Encrypted Message Starts Here ----
Hey *D*, will you serve *X* for me?
Please reply to 5.onion.
---- D's Encrypted Message Ends Here ----
---- A's Signed Message Ends Here ----
A forwarded message, from B to C, looks like::
---- B's Signed Message Starts Here ----
---- A's Signed Message Starts Here ----
To: D's GPG key.
---- D's Encrypted Message Starts Here ----
Hey *D*, will you serve *X* for me?
Please reply to 5.onion.
---- D's Encrypted Message Ends Here ----
---- A's Signed Message Ends Here ----
---- B's Signed Message Ends Here ----
When forwarded over again, from C to D, it looks like::
---- C's Signed Message Starts Here ----
---- A's Signed Message Starts Here ----
To: D's GPG key.
---- D's Encrypted Message Starts Here ----
Hey *D*, will you serve *X* for me?
Please reply to 5.onion.
---- D's Encrypted Message Ends Here ----
---- A's Signed Message Ends Here ----
---- C's Signed Message Ends Here ----
Note that:
- The original message is unchanged.
- Irrelevant signatures (intermediate links in the WOT) are stripped, hiding the
WOT's structure from friends.
Unit Tests
==========
@ -157,4 +259,26 @@ identifies one user to another.
Deception
---------
This is probably the largest worry, where B spoofs A's responses.
This is probably the largest worry, where B fakes A's responses.
Mitigations
===========
We gain a lot by relying on the WOT, and only direct links in the WOT. We also
gain a lot by requiring every communication to be signed (and maximally
encrypted).
Once a key is compromised, we're vulnerable, but to what exactly? What attacks
can an adversary who's compromised a secret key perform? As long as you don't
have any publicly identifiable (or public-facing) services in your Santiago,
then not much. If you're identifiable by your Santiago, and you've permitted
the attacker to see an identifiable service (including your Santiago instances),
that service and all co-located services could be shut down. If the service
identifies you (and not just your box), you're vulnerable. An attacker will
shortly identify all the services you've given it access to.
An attacker can try to identify your friends, though will have trouble if you
send your proxied requests with non-public methods, or you don't proxy at all.
Outstanding Questions
=====================

15
ugly_hacks/setup.sh Normal file
View File

@ -0,0 +1,15 @@
#! /bin/bash
# A crappy, but effective setup script.
#
# This:
#
# - Installs Tor.
# - Adds a hidden service.
# - Sets up Santiago as a hidden service.
#
# This file is distributed under the GPL, version 3 or later, at your
# discretion.
#
# Also see the plugserverr scripts: setup/tor

View File

@ -0,0 +1,43 @@
"""Making Santiago dance, in 4 parts:
- Validating the initial request (playing B).
- Validating the initial response (playing A).
- Validating the silent response.
- Validating the rejection response.
- Validating the acceptance response.
- Validating the forwarded request (playing C).
- Validating the forwarded request (playing D, when C isn't the target).
- Validating the forwarded response.
- Validating the direct response (playing A).
- Validating the indirect response (playing C, B, and A).
"""
import unittest
import santiago
# TODO import some http client and validate my responses!
class TestInitialRequest(unittest.TestCase):
"""Testing the initial request.
Does Santiago produce well-formed output when creating a service request?
"""
def setup(self):
requester = santiago.SantiagoSender("initial_requester")
destination = santiago.SantiagoListener("initial_destination",8081)
def test_valid_request(self):
"""When requested, does Santiago send out an appropriate message?"""
# TODO finish these and otehr tests
requester.request(destination="james", service="wiki")
class TestInitialResponse(unittest.TestCase):
pass
class TestForwardedRequest(unittest.TestCase):
pass
class TestForwardedResponse(unittest.TestCase):
pass