mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-09-19 04:59:01 +00:00
Lots of changes:
- Santiago documentation. - A very basic demo. - Thoughts on tests.
This commit is contained in:
parent
0995358df0
commit
5333d9828a
1
ugly_hacks/fbx/known_services.py
Normal file
1
ugly_hacks/fbx/known_services.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
{ "plinth": { "james": [ "http://192.168.0.12:8080" ], }, }
|
||||||
2
ugly_hacks/fbx/serving_to.py
Normal file
2
ugly_hacks/fbx/serving_to.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
{ "james": { "wiki": "wiki" },
|
||||||
|
"ian": { "web": "plinth" }, }
|
||||||
2
ugly_hacks/fbx/serving_what.py
Normal file
2
ugly_hacks/fbx/serving_what.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
{ "plinth": "http://192.168.0.13:8080",
|
||||||
|
"wiki": "http://192.168.0.13/wiki", }
|
||||||
5
ugly_hacks/fbx/settings.py
Normal file
5
ugly_hacks/fbx/settings.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{ "me": "nick",
|
||||||
|
"socket_port": 8080,
|
||||||
|
"max_hops": 3,
|
||||||
|
"proxy_list": ("tor")
|
||||||
|
}
|
||||||
625
ugly_hacks/santiago.html
Normal file
625
ugly_hacks/santiago.html
Normal 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 "To" 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 -> 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 -> B:</th><td class="field-body">(No response)</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">B -> 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 -> 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: [ "proxy": "proxy", "wiki": "wiki",
|
||||||
|
"drinking buddy": "drinking buddy" ]
|
||||||
|
0x7747: [ "wiki": "wiki", "proxy": "restricted_proxy" ]
|
||||||
|
</pre>
|
||||||
|
</li>
|
||||||
|
<li><p class="first">The Service to Location dictionary. This lists the locations each service
|
||||||
|
runs on:</p>
|
||||||
|
<pre class="literal-block">
|
||||||
|
"wiki": [ 192.168.1.1, "superduperwiki.onion" ]
|
||||||
|
"proxy": [ 8.8.8.8 ]
|
||||||
|
"restricted_proxy": [ 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 -> 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 -> A:</th><td class="field-body">(No response)</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">A -> B:</th><td class="field-body">Will you serve X?</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">B -> 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 -> C:</th><td class="field-body">Will you serve X?</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">C -> A:</th><td class="field-body">No!</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">A -> C:</th><td class="field-body">Will B serve X?</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">C -> B:</th><td class="field-body">Will you serve X for A?</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">B -> 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 -> C:</th><td class="field-body">Will you serve X?</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">C -> A:</th><td class="field-body">(No response)</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">A -> B:</th><td class="field-body">Will you serve X?</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">B -> A:</th><td class="field-body">No!</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">A -> B:</th><td class="field-body">Will C serve X?</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">B -> C:</th><td class="field-body">Will you serve X for A?</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">C -> 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 -> D:</th><td class="field-body">Will you serve X?</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">D -> A:</th><td class="field-body">(No response)</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">A -> B:</th><td class="field-body">Will D serve X for me?</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">B -> C:</th><td class="field-body">Will D serve X for A?</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">C -> D:</th><td class="field-body">Will you serve X for A?</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">D -> A:</th><td class="field-body">Hey, buddy, here's X!</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">A -> D:</th><td class="field-body">(No response)</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">D -> C:</th><td class="field-body">I'm serving X for A.</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">C -> B:</th><td class="field-body">D's serving X for A.</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="field"><th class="field-name">B -> 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
260
ugly_hacks/santiago.py
Normal 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)
|
||||||
@ -24,31 +24,56 @@ for the service.
|
|||||||
information, in the form of:
|
information, in the form of:
|
||||||
|
|
||||||
- Will *X* do *Y* for me?
|
- 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 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?)
|
#. 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
|
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
|
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
|
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
|
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
|
party. If it is acceptably signed (with a known, and valid ID), *B* will
|
||||||
reply to ``A``'s Santiago with an acceptably signed message.
|
reply to *A*'s Santiago with an acceptably signed message.
|
||||||
|
|
||||||
The contents of the request message are as follows:
|
The contents of the request message are as follows:
|
||||||
|
|
||||||
- I am requesting service *X*.
|
- I am requesting service *X*.
|
||||||
- I am requesting that the service be performed by *Y*.
|
- 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
|
The message is signed by *A*, and optionally encrypted (if the message is
|
||||||
proxied, it must contain a "To" header).
|
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
|
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::
|
These data are stored as a triple-key dictionary, with the following mappings::
|
||||||
|
|
||||||
Service -> [ GPG, ID ]
|
Service X: { GPG ID1: [ location, location, location ],
|
||||||
GPG ID -> [ 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
|
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
|
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,
|
:TODO: Revise to reduce communication to logical minimum number of connections,
|
||||||
exchanges, and communications.
|
exchanges, and communications.
|
||||||
|
|
||||||
When ``A`` is connecting to ``B``'s service, it will attempt to connect to that
|
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, which B will validate before permitting the connection. If the service
|
||||||
service?). If the service is non-responsive, ``A`` will query ``B`` for the
|
is non-responsive, *A* will query *B* for the service. If *B* is generally
|
||||||
service. If ``B`` is generally non-responsive, ``A`` will move on to ``C``.
|
non-responsive, *A* will move on to *C*. *A* will ask *C* for the service. If
|
||||||
``A`` will ask ``C`` for the service. If ``C`` cannot provide the service,
|
*C* cannot provide the service, *A* will ask *C* to request the service from
|
||||||
``A`` will ask ``C`` to request the service from ``B``. If ``C`` can reach
|
*B*. If *C* can reach *B* and *B* authorizes *A*, *B* will respond
|
||||||
``B`` and ``B`` authorizes ``A``, ``B`` will respond affirmatively to ``A`` with
|
affirmatively to *A* with the service's location.
|
||||||
the service's location.
|
|
||||||
|
|
||||||
Our Cheats
|
: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.)
|
||||||
Right now, we're cheating. We start by pairing boxes, exchanging both
|
:B -> A: (No response)
|
||||||
box-specific PGP keys and Tor Hidden Service IDs. This allows boxes to trust
|
:A -> B: Will you serve X?
|
||||||
and communicate with one another, regardless of any adverserial interference.
|
:B -> A: (No response, A can't reach B's Santiago.)
|
||||||
Or, rather, any adverserial interference will be obvious and ignorable.
|
:A -> C: Will you serve X?
|
||||||
|
:C -> A: No!
|
||||||
Next Steps
|
:A -> C: Will B serve X?
|
||||||
==========
|
:C -> B: Will you serve X for A?
|
||||||
|
:B -> A: Hey, buddy, here's X!
|
||||||
|
|
||||||
Proxied service requesting
|
Proxied service requesting
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
``Me`` sends a request to ``B``, and ``B`` doesn't respond. ``Me`` requests the
|
The Simple Case
|
||||||
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.
|
|
||||||
|
|
||||||
:TODO: What is the maximum number of hops that we can securely achieve from this
|
I'm looking for somebody to provide a service, *X*.
|
||||||
system while keeping intermedia hops secret? One? More?
|
|
||||||
|
*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
|
Unit Tests
|
||||||
==========
|
==========
|
||||||
@ -157,4 +259,26 @@ identifies one user to another.
|
|||||||
Deception
|
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
15
ugly_hacks/setup.sh
Normal 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
|
||||||
|
|
||||||
43
ugly_hacks/test_santiago.py
Normal file
43
ugly_hacks/test_santiago.py
Normal 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
|
||||||
Loading…
x
Reference in New Issue
Block a user