├── .gitignore ├── README.md ├── cryptotools.py ├── messages.py ├── ncpoc.py ├── network.py ├── paper ├── ncpoc.bib ├── ncpoc.pdf └── ncpoc.tex ├── proof.py └── tests ├── test_messages.py └── test_proof.py /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | 3 | # Latex 4 | *.aux 5 | *.bbl 6 | *.blg 7 | *.out 8 | 9 | # emacs 10 | \#*\# 11 | .#* 12 | 13 | # Byte-compiled / optimized / DLL files 14 | __pycache__/ 15 | *.py[cod] 16 | *$py.class 17 | 18 | # C extensions 19 | *.so 20 | 21 | # Distribution / packaging 22 | .Python 23 | env/ 24 | build/ 25 | develop-eggs/ 26 | dist/ 27 | downloads/ 28 | eggs/ 29 | .eggs/ 30 | lib/ 31 | lib64/ 32 | parts/ 33 | sdist/ 34 | var/ 35 | *.egg-info/ 36 | .installed.cfg 37 | *.egg 38 | 39 | # PyInstaller 40 | # Usually these files are written by a python script from a template 41 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 42 | *.manifest 43 | *.spec 44 | 45 | # Installer logs 46 | pip-log.txt 47 | pip-delete-this-directory.txt 48 | 49 | # Unit test / coverage reports 50 | htmlcov/ 51 | .tox/ 52 | .coverage 53 | .coverage.* 54 | .cache 55 | nosetests.xml 56 | coverage.xml 57 | *,cover 58 | .hypothesis/ 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Django stuff: 65 | *.log 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | #Ipython Notebook 74 | .ipynb_checkpoints 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NameChain (Proof of Concept) 2 | 3 | This is meant to be a proof-of-concept implementation of NameChain, a peer-to-peer verification of HTTP/SSL sites instead of the traditional CA system. 4 | 5 | A blockchain may or may not be involved. 6 | 7 | ## Brief protocol explenation 8 | 9 | The idea is based on both first-trust (like SSH) and the idea that a HTTPS/SSL server should be serving the same SSL certificates to any clients on the internet. 10 | 11 | When a new HTTP/SSL site is "found", the certificate fingerprint is broadcast through the p2p network, along with a proof-of-check. Each participant in the p2p network will (with a random probability) then check the fingerprint and given proof-of-check. The probability that each given client will perform the check is determined by the number of participants in the network, to avoid hugging new sites to death. Or by having decidated "miners" and most clients wokring like an SPV client in Bitcion (i.e. are nodes in the network, but are not miners). 12 | 13 | A new site can be found by anyone, not just it's owner. However, clients probably shouldn't automatically send out any new sites they encounter, as that would tell the whole network what this user is looking at. Either the miners have to crawl or something, or the data can be anonamized somehow. Maybe using Tor to send the data to the miner is plausible, since the delay from finding a new site to having it verified by the network could tolerate delay in seconds. At least its good enough to try. 14 | 15 | Sites could be manually entered into the system, but that seems tedious. 16 | 17 | Depending on the scale, nodes in the p2p network might not be able to hold the full chain 18 | 19 | ## Blockchains 20 | 21 | Everyone wants **a blockchain** these days. A bitcoin-like blockchain probably wont be of much use here, as we went to look up urls's and not transaction id's. 22 | 23 | The word "minig" doesn't really work as an analogy. It's not mining, it's verifying. But "verifier" isn't much of an analogy. 24 | 25 | ## proof-of-check 26 | 27 | Let's try 28 | 29 | ``` 30 | proof-of-check = HMAC(key=node_id, url || ssl_fingerprint) 31 | ``` 32 | 33 | 34 | But anybody can create that hash, if they know the `node_id` and the url. s 35 | -------------------------------------------------------------------------------- /cryptotools.py: -------------------------------------------------------------------------------- 1 | # rename to cryptotools 2 | 3 | import os 4 | import hashlib 5 | 6 | def generate_nodeid(): 7 | return hashlib.sha256(os.urandom(256/8)).hexdigest() 8 | 9 | -------------------------------------------------------------------------------- /messages.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | import json 3 | import cryptotools 4 | 5 | # generate_nodeid() uses SHA256 so this will prevent replay-attacks, 6 | # because every message will have a different nonce. 7 | # It's not nessecary to compare the nonce, HMAC already gives message 8 | # integrety. 9 | nonce = lambda: cryptotools.generate_nodeid() 10 | incr_nonce = lambda env: format(int(env["nonce"], 16) + 1, 'x') 11 | 12 | class InvalidSignatureError(Exception): 13 | pass 14 | 15 | class InvalidNonceError(Exception): 16 | pass 17 | 18 | def make_envelope(msgtype, msg, nodeid): 19 | msg['nodeid'] = nodeid 20 | msg['nonce'] = nonce() 21 | data = json.dumps(msg) 22 | sign = hmac.new(nodeid, data) 23 | envelope = {'data': msg, 24 | 'sign': sign.hexdigest(), 25 | 'msgtype': msgtype} 26 | #print "make_envelope:", envelope 27 | return json.dumps(envelope) 28 | 29 | def envelope_decorator(nodeid, func): 30 | msgtype = func.__name__.split("_")[0] 31 | def inner(*args, **kwargs): 32 | return make_envelope(msgtype, func(*args, **kwargs), nodeid) 33 | return inner 34 | 35 | # ------ 36 | 37 | def create_ackhello(nodeid): 38 | msg = {} 39 | return make_envelope("ackhello", msg, nodeid) 40 | 41 | def create_hello(nodeid, version): 42 | msg = {'version': version} 43 | return make_envelope("hello", msg, nodeid) 44 | 45 | def create_ping(nodeid): 46 | msg = {} 47 | return make_envelope("ping", msg, nodeid) 48 | 49 | def create_pong(nodeid): 50 | msg = {} 51 | return make_envelope("pong", msg, nodeid) 52 | 53 | def create_getaddr(nodeid): 54 | msg = {} 55 | return make_envelope("getaddr", msg, nodeid) 56 | 57 | def create_addr(nodeid, nodes): 58 | msg = {'nodes': nodes} 59 | return make_envelope("addr", msg, nodeid) 60 | # ------- 61 | 62 | def read_envelope(message): 63 | return json.loads(message) 64 | 65 | def read_message(message): 66 | """Read and parse the message into json. Validate the signature 67 | and return envelope['data'] 68 | """ 69 | envelope = json.loads(message) 70 | nodeid = str(envelope['data']['nodeid']) 71 | signature = str(envelope['sign']) 72 | msg = json.dumps(envelope['data']) 73 | verify_sign = hmac.new(nodeid, msg) 74 | #print "read_message:", msg 75 | if hmac.compare_digest(verify_sign.hexdigest(), signature): 76 | return envelope['data'] 77 | else: 78 | raise InvalidSignatureError 79 | -------------------------------------------------------------------------------- /ncpoc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.7 2 | 3 | import argparse 4 | from datetime import datetime 5 | 6 | from twisted.internet import reactor 7 | from twisted.internet.endpoints import TCP4ServerEndpoint, TCP4ClientEndpoint 8 | from twisted.internet.error import CannotListenError 9 | from twisted.internet.endpoints import connectProtocol 10 | 11 | import network 12 | from network import NCFactory, NCProtocol 13 | 14 | def _print(*args): 15 | # double, make common module 16 | time = datetime.now().time().isoformat()[:8] 17 | print time, 18 | print "".join(map(str, args)) 19 | 20 | 21 | # Move this and network.BOOTSTRAP_NODES somewhere mode sensible 22 | DEFAULT_PORT = 5005 23 | 24 | parser = argparse.ArgumentParser(description="ncpoc") 25 | parser.add_argument('--port', type=int, default=DEFAULT_PORT) 26 | parser.add_argument('--listen', default="127.0.0.1") 27 | parser.add_argument('--bootstrap', action="append", default=[]) 28 | 29 | if __name__ == "__main__": 30 | args = parser.parse_args() 31 | try: 32 | endpoint = TCP4ServerEndpoint(reactor, args.port, interface=args.listen) 33 | _print(" [ ] LISTEN:", args.listen, ":", args.port) 34 | ncfactory = NCFactory() 35 | endpoint.listen(ncfactory) 36 | except CannotListenError: 37 | _print("[!] Address in use") 38 | raise SystemExit 39 | 40 | 41 | # connect to bootstrap addresses 42 | _print(" [ ] Trying to connect to bootstrap hosts:") 43 | for bootstrap in network.BOOTSTRAP_NODES + [a+":"+str(DEFAULT_PORT) for a in args.bootstrap]: 44 | 45 | _print(" [*] ", bootstrap) 46 | host, port = bootstrap.split(":") 47 | point = TCP4ClientEndpoint(reactor, host, int(port)) 48 | d = connectProtocol(point, NCProtocol(ncfactory, "SENDHELLO", "SPEAKER")) 49 | d.addCallback(network.gotProtocol) 50 | reactor.run() 51 | -------------------------------------------------------------------------------- /network.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from time import time 3 | from functools import partial 4 | 5 | from twisted.internet import reactor 6 | from twisted.internet.protocol import Protocol, Factory 7 | from twisted.internet.endpoints import TCP4ClientEndpoint 8 | from twisted.internet.endpoints import connectProtocol 9 | from twisted.internet.task import LoopingCall 10 | 11 | import messages 12 | import cryptotools 13 | 14 | PING_INTERVAL = 1200.0 # 20 min = 1200.0 15 | BOOTSTRAP_NODES = ["localhost:5008", 16 | "localhost:5007", 17 | "localhost:5006", 18 | "localhost:5005"] 19 | 20 | def _print(*args): 21 | time = datetime.now().time().isoformat()[:8] 22 | print time, 23 | print " ".join(map(str, args)) 24 | 25 | class NCProtocol(Protocol): 26 | def __init__(self, factory, state="GETHELLO", kind="LISTENER"): 27 | self.factory = factory 28 | self.state = state 29 | self.VERSION = 0 30 | self.remote_nodeid = None 31 | self.kind = kind 32 | self.nodeid = self.factory.nodeid 33 | self.lc_ping = LoopingCall(self.send_PING) 34 | self.message = partial(messages.envelope_decorator, self.nodeid) 35 | 36 | def connectionMade(self): 37 | r_ip = self.transport.getPeer() 38 | h_ip = self.transport.getHost() 39 | self.remote_ip = r_ip.host + ":" + str(r_ip.port) 40 | self.host_ip = h_ip.host + ":" + str(h_ip.port) 41 | 42 | def print_peers(self): 43 | if len(self.factory.peers) == 0: 44 | _print(" [!] PEERS: No peers connected.") 45 | else: 46 | _print(" [ ] PEERS:") 47 | for peer in self.factory.peers: 48 | addr, kind = self.factory.peers[peer][:2] 49 | _print(" [*]", peer, "at", addr, kind) 50 | 51 | def write(self, line): 52 | self.transport.write(line + "\n") 53 | 54 | def connectionLost(self, reason): 55 | # NOTE: It looks like the NCProtocol instance will linger in memory 56 | # since ping keeps going if we don't .stop() it. 57 | try: self.lc_ping.stop() 58 | except AssertionError: pass 59 | 60 | try: 61 | self.factory.peers.pop(self.remote_nodeid) 62 | if self.nodeid != self.remote_nodeid: 63 | self.print_peers() 64 | except KeyError: 65 | if self.nodeid != self.remote_nodeid: 66 | _print(" [ ] GHOST LEAVES: from", self.remote_nodeid, self.remote_ip) 67 | 68 | def dataReceived(self, data): 69 | for line in data.splitlines(): 70 | line = line.strip() 71 | envelope = messages.read_envelope(line) 72 | if self.state in ["GETHELLO", "SENTHELLO"]: 73 | # Force first message to be HELLO or crash 74 | if envelope['msgtype'] == 'hello': 75 | self.handle_HELLO(line) 76 | else: 77 | _print(" [!] Ignoring", envelope['msgtype'], "in", self.state) 78 | else: 79 | if envelope['msgtype'] == 'ping': 80 | self.handle_PING(line) 81 | elif envelope['msgtype'] == 'pong': 82 | self.handle_PONG(line) 83 | elif envelope['msgtype'] == 'addr': 84 | self.handle_ADDR(line) 85 | 86 | def send_PING(self): 87 | _print(" [>] PING to", self.remote_nodeid, "at", self.remote_ip) 88 | ping = messages.create_ping(self.nodeid) 89 | self.write(ping) 90 | 91 | def handle_PING(self, ping): 92 | if messages.read_message(ping): 93 | pong = messages.create_pong(self.nodeid) 94 | self.write(pong) 95 | 96 | def send_ADDR(self): 97 | _print(" [>] Telling " + self.remote_nodeid + " about my peers") 98 | # Shouldn't this be a list and not a dict? 99 | peers = self.factory.peers 100 | listeners = [(n, peers[n][0], peers[n][1], peers[n][2]) 101 | for n in peers] 102 | addr = messages.create_addr(self.nodeid, listeners) 103 | self.write(addr) 104 | 105 | def handle_ADDR(self, addr): 106 | try: 107 | nodes = messages.read_message(addr)['nodes'] 108 | _print(" [<] Recieved addr list from peer " + self.remote_nodeid) 109 | #for node in filter(lambda n: nodes[n][1] == "SEND", nodes): 110 | for node in nodes: 111 | _print(" [*] " + node[0] + " " + node[1]) 112 | if node[0] == self.nodeid: 113 | _print(" [!] Not connecting to " + node[0] + ": thats me!") 114 | return 115 | if node[1] != "SPEAKER": 116 | _print(" [ ] Not connecting to " + node[0] + ": is " + node[1]) 117 | return 118 | if node[0] in self.factory.peers: 119 | _print(" [ ] Not connecting to " + node[0] + ": already connected") 120 | return 121 | _print(" [ ] Trying to connect to peer " + node[0] + " " + node[1]) 122 | # TODO: Use [2] and a time limit to not connect to "old" peers 123 | host, port = node[0].split(":") 124 | point = TCP4ClientEndpoint(reactor, host, int(port)) 125 | d = connectProtocol(point, NCProtocol(ncfactory, "SENDHELLO", "SPEAKER")) 126 | d.addCallback(gotProtocol) 127 | except messages.InvalidSignatureError: 128 | print addr 129 | _print(" [!] ERROR: Invalid addr sign ", self.remote_ip) 130 | self.transport.loseConnection() 131 | 132 | def handle_PONG(self, pong): 133 | pong = messages.read_message(pong) 134 | _print(" [<] PONG from", self.remote_nodeid, "at", self.remote_ip) 135 | # hacky 136 | addr, kind = self.factory.peers[self.remote_nodeid][:2] 137 | self.factory.peers[self.remote_nodeid] = (addr, kind, time()) 138 | 139 | def send_HELLO(self): 140 | hello = messages.create_hello(self.nodeid, self.VERSION) 141 | #_print(" [ ] SEND_HELLO:", self.nodeid, "to", self.remote_ip) 142 | self.transport.write(hello + "\n") 143 | self.state = "SENTHELLO" 144 | 145 | def handle_HELLO(self, hello): 146 | try: 147 | hello = messages.read_message(hello) 148 | self.remote_nodeid = hello['nodeid'] 149 | if self.remote_nodeid == self.nodeid: 150 | _print(" [!] Found myself at", self.host_ip) 151 | self.transport.loseConnection() 152 | else: 153 | if self.state == "GETHELLO": 154 | my_hello = messages.create_hello(self.nodeid, self.VERSION) 155 | self.transport.write(my_hello + "\n") 156 | self.add_peer() 157 | self.state = "READY" 158 | self.print_peers() 159 | #self.write(messages.create_ping(self.nodeid)) 160 | if self.kind == "LISTENER": 161 | # The listener pings it's audience 162 | _print(" [ ] Starting pinger to " + self.remote_nodeid) 163 | self.lc_ping.start(PING_INTERVAL, now=False) 164 | # Tell new audience about my peers 165 | self.send_ADDR() 166 | except messages.InvalidSignatureError: 167 | _print(" [!] ERROR: Invalid hello sign ", self.remoteip) 168 | self.transport.loseConnection() 169 | 170 | def add_peer(self): 171 | entry = (self.remote_ip, self.kind, time()) 172 | self.factory.peers[self.remote_nodeid] = entry 173 | 174 | # Splitinto NCRecvFactory and NCSendFactory (also reconsider the names...:/) 175 | class NCFactory(Factory): 176 | def __init__(self): 177 | pass 178 | 179 | def startFactory(self): 180 | self.peers = {} 181 | self.numProtocols = 0 182 | self.nodeid = cryptotools.generate_nodeid()[:10] 183 | _print(" [ ] NODEID:", self.nodeid) 184 | 185 | def stopFactory(self): 186 | pass 187 | 188 | def buildProtocol(self, addr): 189 | return NCProtocol(self, "GETHELLO", "LISTENER") 190 | 191 | def gotProtocol(p): 192 | # ClientFactory instead? 193 | p.send_HELLO() 194 | -------------------------------------------------------------------------------- /paper/ncpoc.bib: -------------------------------------------------------------------------------- 1 | % 2 | % Stuff from ardrand left as example 3 | % 4 | 5 | @ARTICLE{anthes2011, 6 | author = {Gary Anthes}, 7 | title = {{The Quest for Randomness}}, 8 | journal = {{CACM}}, 9 | year = {2011}, 10 | volume = {54}, 11 | pages = {13-15}, 12 | number = {4}, 13 | file = {anthes2011.pdf:anthes2011.pdf:PDF}, 14 | keywords = {random number generation}, 15 | timestamp = {2011.04.30} 16 | } 17 | 18 | @ARTICLE{bitcoin2008, 19 | author = {Satoshi Nakamoto}, 20 | title = {{Bitcoin: A Peer-to-Peer Electronic Cash System}}, 21 | year = {2008}, 22 | file = {bitcoin.pdf:bitcoin.pdf:PDF}, 23 | keywords = {random number generation}, 24 | timestamp = {2008.10.31} 25 | } 26 | 27 | 28 | @misc{mozillacawiki, 29 | author = "Mozilla Project", 30 | title = "Mozilla CA Program Overview", 31 | howpublished = {\url{https://wiki.mozilla.org/CA:Overview}}, 32 | note = {Accessed: Feb 2015} 33 | } 34 | 35 | @misc{mozillacapolicy, 36 | author = "Mozilla Project", 37 | title = "Mozilla CA Certificate Policy", 38 | howpublished = {\url{https://wiki.mozilla.org/CA:Overview}}, 39 | note = {Accessed: Feb 2015} 40 | } 41 | 42 | @misc{ncpocgithub, 43 | author = "Benedikt Kristinsson", 44 | title = {benediktkr/ncpoc on GitHub}, 45 | howpublished = {\url{https://github.com/benediktkr/ncpoc}}, 46 | note = {Accessed: \today} 47 | } 48 | 49 | @article{lrng, 50 | author = {{Zvi Gutterman, Benny Pinkas, Tzachy Reinman}}, 51 | title = {{Analysis of the Linux Random Number Generator}}, 52 | journal = {{Security and Privacy}}, 53 | pages = {385-400}, 54 | year = "2006", 55 | timestamp = "2011.12.05" 56 | } 57 | 58 | @BOOK{schneier1996, 59 | title = {Applied Cryptography}, 60 | year = {1996}, 61 | author = {Bruce Schneier}, 62 | edition = "second edition", 63 | owner = {kristjan}, 64 | publisher = {John wiley \& Sons}, 65 | timestamp = {2010.09.20} 66 | } 67 | 68 | -------------------------------------------------------------------------------- /paper/ncpoc.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benediktkr/ncpoc/d3a3b48715ee9af664a59b49f4a4881352dd8fc8/paper/ncpoc.pdf -------------------------------------------------------------------------------- /paper/ncpoc.tex: -------------------------------------------------------------------------------- 1 | \documentclass[a4paper]{article} % Switch to report for front page 2 | \renewcommand{\refname}{References} 3 | \usepackage{amsmath,amsfonts,amsthm,amssymb} 4 | \usepackage[utf8]{inputenc} 5 | %\usepackage[icelandic]{babel} 6 | \usepackage[T1]{fontenc} 7 | \usepackage{setspace} % Allows more fine-grained control over line spacing 8 | \usepackage{fancyhdr} % Headers and footers 9 | \usepackage{lastpage} % Allows references to the last page of the document 10 | \usepackage{chngpage} % Change format mid-page ? 11 | \usepackage{soul} % Highlights text, with \hl{} 12 | \usepackage[usenames,dvipsnames]{color} % Add color to text 13 | \usepackage{graphicx,float,wrapfig} 14 | \usepackage{ifthen} % \ifthenelse{} 15 | \usepackage{listings} 16 | \usepackage{courier} % Write in a monospace font 17 | %\usepackage{geometry} % Because 'fullpage' is outdated 18 | \usepackage{hyperref} 19 | \usepackage[usenames,dvipsnames]{color} 20 | \usepackage{subfig} 21 | \usepackage{placeins} 22 | 23 | \newtheorem{mydef}{Definition} 24 | \newcommand{\Title}{A proof-of-concept implementation} 25 | \newcommand{\SubTitle}{A distributed peer-to-peer ledger for trust in HTTPS} 26 | \newcommand{\DueDate}{\today} % Or \today 27 | \newcommand{\Class}{NameChain} 28 | \newcommand{\AuthorClearSpace}{3in} % How far below the title the author name shoudl appear 29 | \newcommand{\ClassInstructor}{} 30 | \newcommand{\AuthorName}{Benedikt Kristinsson} 31 | \newcommand{\DueLang}{} % Icelandic (perhaps some ifelse on language pack) 32 | %\newcommand{\DueLang}{Due on} % English 33 | 34 | 35 | %\topmargin=-0.45in 36 | %\evensidemargin=0in 37 | %\oddsidemargin=0in 38 | %\textwidth=6.5in 39 | %\textheight=9.0in 40 | %\headsep=0.25in 41 | 42 | % This is the color used for comments below 43 | \definecolor{MyDarkGreen}{rgb}{0.0,0.4,0.0} 44 | \definecolor{MyDarkRed}{rgb}{0.4,0.0,0.0} 45 | 46 | 47 | \lstloadlanguages{Python} 48 | \lstset{language=Python, 49 | %frame=single, % Single frame around code 50 | %basicstyle=\small\ttfamily, % Use small true type font 51 | basicstyle=\small, % Don't use ttf 52 | keywordstyle=[1]\color{Blue}, %\bf % functions green (bold commented out) 53 | keywordstyle=[2]\color{Green}, % function arguments purple 54 | keywordstyle=[3]\color{Red}\underbar, % User functions underlined and blue 55 | identifierstyle=, 56 | commentstyle=\usefont{T1}{pcr}{m}{sl}\color{MyDarkRed}\small, 57 | stringstyle=\color{MyDarkGreen}, % Strings 58 | showstringspaces=false, % Don't put marks in string spaces 59 | tabsize=4, 60 | % To add more keywords 61 | %morekeywords={}, 62 | % Function parameters 63 | %morekeywords=[2]{on, off, interp}, 64 | %%% Put user defined functions here 65 | %morekeywords=[3]{FindESS, homework_example}, 66 | % 67 | %morecomment=[l][\color{Grey}]{...}, % Line continuation (...) like blue comment 68 | %numbers=left, % Line numbers on left 69 | %firstnumber=1, % Line numbers start with line 1 70 | %numberstyle=\tiny\color{Grey}, % Line numbers are blue 71 | %stepnumber=1 % Line numbers go in steps of 1 72 | } 73 | 74 | % Setup the header and footer 75 | %\pagestyle{fancy} % Pagestyle allows for header/foother 76 | \pagestyle{plain} % No header/footerer 77 | \lhead{\AuthorName} 78 | \chead{\SubTitle} 79 | \rhead{\Class} 80 | %\cfoot{\thepage} 81 | % \rfoot{Page\ \thepage\ of\ \protect\pageref{LastPage}} 82 | \renewcommand\headrulewidth{0.4pt} 83 | %\renewcommand\footrulewidth{0.4pt} 84 | % This is used to trace down (pin point) problems in latexing a document: 85 | %\tracingall 86 | 87 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 88 | % Some tools 89 | 90 | % Includes a figure 91 | % The first parameter is the label, which is also the name of the figure 92 | % with or without the extension (e.g., .eps, .fig, .png, .gif, etc.) 93 | % IF NO EXTENSION IS GIVEN, LaTeX will look for the most appropriate one. 94 | % The second parameter is the width of the figure normalized to column width 95 | % (e.g. 0.5 for half a column, 0.75 for 75% of the column) 96 | % The third parameter is the caption. 97 | \newcommand{\scalefig}[3]{ 98 | \begin{figure}[ht!] 99 | % Requires \usepackage{graphicx} 100 | \centering 101 | \includegraphics[width=#2\columnwidth]{#1} 102 | %%% I think \captionwidth (see above) can go away as long as 103 | %%% \centering is above 104 | %\captionwidth{#2\columnwidth}% 105 | \caption{#3} 106 | \label{#1} 107 | \end{figure}} 108 | 109 | % Includes code 110 | % The first parameter is the label, which also is the name of the script 111 | % with the file extension the '#1' in the command belove 112 | % The second parameter is the optional caption. 113 | \newcommand{\code}[2] 114 | {\begin{itemize}\item[]\lstinputlisting[caption=#2,label=#1]{#1}\end{itemize}} 115 | 116 | 117 | %\setcounter{secnumdepth}{0} 118 | \newcommand{\problem}[2] 119 | { 120 | \subsubsection*{\sc{#1}} 121 | #2 122 | } 123 | \newcommand{\tmpsection}[1]{} 124 | \let\tmpsection=\section 125 | 126 | \renewcommand{\section}[2]{ 127 | 128 | \ifthenelse{ 129 | \equal{#2}{*} % I have to be oddly specific here 130 | } 131 | { 132 | \tmpsection{References} 133 | \tmpsection{\sc{#2} } 134 | } 135 | {\tmpsection{\sc{#1} } } 136 | 137 | 138 | } 139 | 140 | 141 | \title{ 142 | \Class:\ \Title 143 | %\ifthenelse{\equal{\SubTitle}{}}{}{\\{\SubTitle}} 144 | } 145 | \date{\small{\DueLang\ \DueDate}} 146 | \author{\AuthorName\\Advisor: \ClassInstructor\\} 147 | 148 | 149 | \begin{document} 150 | \maketitle 151 | 152 | 153 | % Uncomment the \tableofcontents and \newpage lines to get a Contents page 154 | % Uncomment the \setcounter line as well if you do NOT want subsections 155 | % listed in Contents 156 | % Remeber to compile twice 157 | %\setcounter{tocdepth}{1} 158 | %\tableofcontents 159 | %\newpage 160 | 161 | %\clearpage 162 | %x\section{Lausn verkefnis og útfærsla} 163 | 164 | \begin{abstract} 165 | 166 | Traditionally we have come to rely on Certificate Authroities (CA) 167 | to verify that the servers we connect to are the ones we are relly 168 | trying to connect to. The CA system is very centralized and more 169 | based on beurocracy than technology. 170 | 171 | In this essay we propose a distributed and peer-to-peer techonology 172 | that is builds a distributed consent of what encryption keys a 173 | client should expect a remote server to use. Rather than having to 174 | trust one central authority to tell the truth, the network forms a 175 | majority that is trusted. 176 | 177 | \end{abstract} 178 | 179 | \section{Introduction} 180 | % Motivation 181 | 182 | % Example of how to write code in this template 183 | 184 | The current CA model is broken. \\ 185 | 186 | Operating systems and browsers come pre-loaded with CA certificates 187 | and will blindly trust their signatures. One rouge CA being 188 | distributed in such a way would make dragnet attacks almost trivial, 189 | and begs the question of wether they have been used for targetted 190 | attacks. In a post-Snowden world, we have to consider these to be 191 | facts, rather than mere speculations. \\ 192 | 193 | 194 | \subsection{Contributions} 195 | 196 | The contributions of this work are the following: 197 | 198 | \begin{itemize} 199 | 200 | \item An implementation of a peer-to-peer network with Python and Twisted 201 | 202 | \end{itemize} 203 | 204 | \section{Vendor-shipped CA certificates} 205 | 206 | The CA model instills trusts by having Certificates Authorities sign 207 | certificates. For the operating system or browser to be able to verify 208 | this signature, they need to maintain a list of known CA certificates, 209 | called \textit{root certificates}. These lists come pre-shipped with 210 | the software and are thus hard to maintain and keep up-to-date. \\ 211 | 212 | If any of these root certificates have signed a certificate for any 213 | given domain, the software will accept the signature as valid and, 214 | more importantly - that the remote server is who it claims to be. If a 215 | state-level actor has control over just one of the root certificates 216 | distributed (hereby referred to be as a \textit{rouge certificate}) 217 | then they can man-in-the-middle the connection 218 | trivially. Occasionally, there have been doubts about the legitimacy 219 | of some of these certificates\footnote{dig up examples} and the 220 | Chinese government has at least one broadly distributed 221 | certificate. \\ 222 | 223 | Mozilla Project publishes their CA Certificate 224 | Policy~\cite{mozillacapolicy} which details the application process, 225 | as well as how Mozilla maintains trust in the root certificates. More 226 | detailed discussion is mainted on the Mozilla 227 | Wiki~\cite{mozillacawiki}, including lists of current CA certificates 228 | as well as a list of all removed CA certificates (although it does not 229 | detail the reasons for the removal).\\ 230 | 231 | %Microsoft Trusted Root Certificate, published program requirements\footnote{https://technet.microsoft.com/en-us/library/cc751157.aspx} and there are some Membership Lists\footnote{http://download.microsoft.com/download/1/5/7/157B29AB-F890-464A-995A-C87945B28E5A/Windows\%20Root\%20Certificate\%20Program\%20Members\%20-\%20Sept\%202014.pdf} as PDFs on Microsoft Download.\\ 232 | 233 | 234 | \section{Protocol description} 235 | 236 | NameChain is a peer-to-peer network to validate certificates with a 237 | majority consensus protocol. It is heavily influenced by the structure 238 | of Bitcoin~\cite{bitcoin2008}. As the proof-of-concept implemenation 239 | is not stable software and might be worked on after the finalization 240 | this essay, the protocol might change. Readers are directed towards 241 | the project on GitHub~\cite{ncpocgithub} for an up-to-date protocol 242 | description. Best effors are made to keep the \texttt{README.md} file 243 | for the project accuarte, but ultimately the protocol is specified by 244 | the sorce code. \\ 245 | 246 | \section{Proof-of-concept implementation} 247 | 248 | 249 | The proof-of-concept implemenation is written in Python and uses 250 | Twisted\footnote{\url{https://twistedmatrix.com/}} to implement the 251 | protocol and peer-to-peer network. The code is hosted and maintained 252 | on GitHub~\cite{ncpocgithub}. Messages are serialized to JSON-strings 253 | before being sent over the network, wrapped in an JSON envelope with a 254 | HMAC signature. \\ 255 | 256 | \begin{lstlisting}[caption=JSON envelope structure] 257 | def make_envelope(msgtype, msg, nodeid): 258 | msg['nodeid'] = nodeid 259 | msg['nonce'] = nonce() 260 | sign = hmac.new(nodeid, json.dumps(msg)) 261 | envelope = {'data': msg, 262 | 'sign': sign.hexdigest(), 263 | 'msgtype': msgtype} 264 | return json.dumps(envelope) 265 | \end{lstlisting} 266 | 267 | Seeing as this is a personal research PoC implementation, broad public 268 | use is not anticipated. The author currently maintains bootstrapping 269 | nodes at \texttt{freespace.sudo.is} and \texttt{ncnode.sudo.is}, with 270 | no guarantee of uptime. 271 | 272 | 273 | %\section{Case study: pre-loaded CA certificates} 274 | % 275 | %\subsection{Firefox nightly} 276 | % 277 | %\subsection{Windows 7} 278 | % 279 | %\subsection{Debian} 280 | % 281 | %\subsection{Ubuntu} 282 | 283 | 284 | 285 | \bibliographystyle{plain} 286 | \bibliography{ncpoc} 287 | 288 | 289 | \end{document} 290 | 291 | % Stuff to mention 292 | % Switching from 9600 baudrate to the new one resulting in leastsignrand passing poker test 293 | % Also resulted in SerialException 294 | % How closely the algs miss the FIPS test 295 | % Bitrates in 9600 vs new one 296 | % Check other baudrates? 297 | 298 | % Eftir að gera 299 | %% Sýna graf frá einum Arduino þar sem lesið er frá öllum pinnum 300 | %% Athgua hvort að ard3 sýni drop á skógarbraut. Taka gröf úr ard2 líka. Skoða ard1 301 | %% Sýna munin á gildum í mismuandi herbergjum (fossahvarf vs skógarbraut) 302 | 303 | % Athugasemdir til Ýmis: 304 | %% Mig vantar að orða tengsl við Tsense einhversstaðar 305 | %%% Tsense notaði einmitt aðferðina að lesa analogRead til að búa seed, koma því að 306 | %% Eiga öll NIST security levels erindi í skýrsluna? 307 | %% Ég set footnote á randomSeed referencið í introduction en myndi líka að þurfa að vitna í það í seinni kafla. Hvernig geri ég það snyrtilegast? 308 | -------------------------------------------------------------------------------- /proof.py: -------------------------------------------------------------------------------- 1 | import ssl 2 | from hashlib import sha256 3 | 4 | class ProofError(Exception): 5 | pass 6 | 7 | def get_https_fingerprint(url): 8 | pem = ssl.get_server_certificate((url, 443), ca_certs=None) 9 | # not the correct way to get fingerprint 10 | return sha256(pem).hexdigest() 11 | 12 | def construct_proof(url, fingerprint, nodeid): 13 | """The variable `nodeid` serves as a nonce and should perhaps be 14 | substituted for a random integer? 15 | 16 | The idea here is to calculate a unique hash that requires actually 17 | checking the server certificate. 18 | """ 19 | return sha256(fingerprint + url + nodeid).hexdigest() 20 | 21 | def proof_of_check(url, nodeid): 22 | fingerprint = get_https_fingerprint(url) 23 | proof = construct_proof(url, fingerprint, nodeid) 24 | return fingerprint, proof 25 | 26 | def verify_proof(url, fingerprint, nodeid, claimed_proof): 27 | if get_https_fingerprint(url) != fingerprint: 28 | return False 29 | elif construct_proof(url, fingerprint, nodeid) != claimed_proof: 30 | return False 31 | else: 32 | return True 33 | 34 | -------------------------------------------------------------------------------- /tests/test_messages.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append("..") 3 | import unittest 4 | 5 | import messages 6 | import cryptotools 7 | 8 | class TestMessages(unittest.TestCase): 9 | def setUp(self): 10 | self.nodeid = cryptotools.generate_nodeid() 11 | 12 | def tearDown(self): 13 | pass 14 | 15 | def test_hello(self): 16 | hello = messages.create_hello(self.nodeid, 0) 17 | # check that InvalidSignatureError is not raised 18 | return messages.read_message(hello) 19 | 20 | def test_ackhello(self): 21 | ackhello = messages.create_ackhello(self.nodeid) 22 | return messages.read_message(ackhello) 23 | 24 | def test_ping(self): 25 | ping = messages.create_ping(self.nodeid) 26 | # check that InvalidSignatureError isn't raised 27 | return messages.read_message(ping) 28 | 29 | def test_pong(self): 30 | pong = messages.create_pong(self.nodeid) 31 | # exceptions? 32 | return messages.read_message(pong) 33 | 34 | if __name__ == "__main__": 35 | unittest.main() 36 | -------------------------------------------------------------------------------- /tests/test_proof.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append("..") 3 | import unittest 4 | 5 | import proof 6 | import cryptotools 7 | 8 | class TestProof(unittest.TestCase): 9 | def setUp(self): 10 | self.nodeid = cryptotools.generate_nodeid() 11 | 12 | def tearDown(self): 13 | pass 14 | 15 | def test_proof(self): 16 | for url in ["sudo.is", "lokun.is", "microsoft.com"]: 17 | pocheck = proof.proof_of_check(url, self.nodeid) 18 | ver = proof.verify_proof(url, pocheck[0], self.nodeid, pocheck[1]) 19 | self.assertTrue(ver) 20 | 21 | 22 | if __name__ == "__main__": 23 | unittest.main() 24 | --------------------------------------------------------------------------------