Skip to content

Commit

Permalink
Created NotaryParser class, removing parsing methods from Notary and …
Browse files Browse the repository at this point in the history
…Notaries.

Created default_notaries() function, removing Notaries.default() method.
  • Loading branch information
von committed Dec 20, 2011
1 parent 1e344ea commit 747bc7b
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 107 deletions.
37 changes: 0 additions & 37 deletions Perspectives/Notaries.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
"""Notaries: List of Notary instances"""
import httplib
import logging
import pkgutil
import random
import StringIO

from Notary import Notary
from Exceptions import NotaryException
Expand All @@ -20,41 +18,6 @@ def __init__(self):
self.logger = logging.getLogger("Perspectives.Notary")
list.__init__(self)

@classmethod
def default(cls):
"""Return the default Notaries"""
data = pkgutil.get_data("Perspectives", "conf/http_notary_list.txt")
return cls.from_stream(StringIO.StringIO(data))

@classmethod
def from_file(cls, file_path):
"""Return Notaries described in file.
See from_stream() for expected format."""
with file(file_path, "r") as f:
notaries = cls.from_stream(f)
return notaries

@classmethod
def from_stream(cls, stream):
"""Return Notaries described in given stream.
Expected format for each Notary is:
# Lines starting with '#' are comments and ignored
<hostname>:<port>
-----BEGIN PUBLIC KEY-----
<multiple lines of Base64-encoded data>
-----END PUBLIC KEY----
"""
notaries = cls()
while True:
notary = cls.NotaryClass.from_stream(stream)
if notary is None: # EOF
break
else:
notaries.append(notary)
return notaries

def query(self, service, num=0, timeout=10):
"""Query Notaries and return NotaryResponses instance
Expand Down
54 changes: 0 additions & 54 deletions Perspectives/Notary.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"""Class for representing Perspective Notaries"""

import logging
import M2Crypto
import re
import struct
import urllib

Expand Down Expand Up @@ -52,58 +50,6 @@ def get_url(self, service):
url = "http://%s:%s/?host=%s&port=%s&service_type=%s" % (self.hostname, self.port, service.hostname, service.port, service.type)
return url

@classmethod
def from_stream(cls, stream):
"""Return Notary described in given stream.
Expected format is:
# Lines starting with '#' are comments and ignored
<hostname>:<port>
-----BEGIN PUBLIC KEY-----
<multiple lines of Base64-encoded data>
-----END PUBLIC KEY----
If EOF is found before a Notary, returns None.
"""
hostname, port, public_key = None, None, None
hostname_port_re = re.compile("(\S+):(\d+)")
for line in stream:
line = line.strip()
if line.startswith("#") or (line == ""):
continue # Ignore comments and blank lines
match = hostname_port_re.match(line)
if match is not None:
hostname = match.group(1)
port = int(match.group(2))
elif line == "-----BEGIN PUBLIC KEY-----":
if hostname is None:
raise NotaryException("Public key found without Notary")
lines = [line + "\n"]
for line in stream:
lines.append(line)
if line.startswith("-----END PUBLIC KEY-----"):
break
else:
raise NotaryException("No closing 'END PUBLIC KEY' line for key found")
public_key = cls._public_key_from_lines(lines)
break # End of Notary
else:
raise NotaryException("Unrecognized line: " + line)
if hostname is None:
# We hit EOF before finding a Notary
return None
if public_key is None:
raise NotaryException("No public key found for Notary %s:%s" % (hostname, port))
return cls(hostname, port, public_key)

@classmethod
def _public_key_from_lines(cls, lines):
"""Read and return public key from lines"""
bio = M2Crypto.BIO.MemoryBuffer("".join(lines))
pub_key = M2Crypto.EVP.PKey()
pub_key.assign_rsa(M2Crypto.RSA.load_pub_key_bio(bio))
return pub_key

def verify_response(self, response, service):
"""Verify signature of response regarding given service.
Expand Down
92 changes: 92 additions & 0 deletions Perspectives/NotaryParser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""Parser for Notaries"""

import M2Crypto
import re

from Exceptions import NotaryException
from Notary import Notary
from Notaries import Notaries

class NotaryParser:
"""Parse serialized Notaries and return a Notaries instance"""

def parse_file(self, path):
"""Return Notaries described in file.
See parse_stream() for expected format"""
with file(path, "r") as stream:
notaries = self.parse_stream(stream)
return notaries

def parse_stream(self, stream):
"""Return Notaries described in stream.
Expected format for each Notary is:
# Lines starting with '#' are comments and ignored
<hostname>:<port>
-----BEGIN PUBLIC KEY-----
<multiple lines of Base64-encoded data>
-----END PUBLIC KEY----
"""
notaries = Notaries()
while True:
notary = self._parse_notary(stream)
if notary is None: # EOF
break
else:
notaries.append(notary)
return notaries

hostname_port_re = re.compile("(\S+):(\d+)")

def _parse_notary(self, stream):
"""Return Notary described in given stream.
Expected format is:
# Lines starting with '#' are comments and ignored
<hostname>:<port>
-----BEGIN PUBLIC KEY-----
<multiple lines of Base64-encoded data>
-----END PUBLIC KEY----
If EOF is found before a Notary, returns None.
"""
hostname, port, public_key = None, None, None
for line in stream:
line = line.strip()
if line.startswith("#") or (line == ""):
continue # Ignore comments and blank lines
match = self.hostname_port_re.match(line)
if match is not None:
hostname = match.group(1)
port = int(match.group(2))
elif line == "-----BEGIN PUBLIC KEY-----":
if hostname is None:
raise NotaryException("Public key found without Notary")
lines = [line + "\n"]
for line in stream:
lines.append(line)
if line.startswith("-----END PUBLIC KEY-----"):
break
else:
raise NotaryException(
"No closing 'END PUBLIC KEY' line for key found")
public_key = self._public_key_from_lines(lines)
break # End of Notary
else:
raise NotaryException("Unrecognized line: " + line)
if hostname is None:
# We hit EOF before finding a Notary
return None
if public_key is None:
raise NotaryException(
"No public key found for Notary %s:%s" % (hostname, port))
return Notary(hostname, port, public_key)

@classmethod
def _public_key_from_lines(cls, lines):
"""Read and return public key from lines"""
bio = M2Crypto.BIO.MemoryBuffer("".join(lines))
pub_key = M2Crypto.EVP.PKey()
pub_key.assign_rsa(M2Crypto.RSA.load_pub_key_bio(bio))
return pub_key
2 changes: 2 additions & 0 deletions Perspectives/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Make these classes available via 'from Perspectives import ...'
from default_notaries import default_notaries
from Exceptions import PerspectivesException
from Exceptions import FingerprintException
from Exceptions import NotaryException
Expand All @@ -8,6 +9,7 @@
from Fingerprint import Fingerprint
from Notary import Notary
from Notaries import Notaries
from NotaryParser import NotaryParser
from NotaryResponse import NotaryResponse
from NotaryResponse import ServiceKey
from NotaryResponses import NotaryResponses
Expand Down
12 changes: 12 additions & 0 deletions Perspectives/default_notaries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Default Notaries"""

import pkgutil
import StringIO

from NotaryParser import NotaryParser

def default_notaries():
"""Return the default Notaries"""
parser = NotaryParser()
data = pkgutil.get_data("Perspectives", "conf/http_notary_list.txt")
return parser.parse_stream(StringIO.StringIO(data))
24 changes: 14 additions & 10 deletions unittests/Notaries_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,31 @@

class TestNotaries(unittest.TestCase):
"""Tests for Notaries class"""

my_path = os.path.dirname(os.path.abspath( __file__ ))

def _load_notaries(self):
from Perspectives import Notaries
return Notaries.from_file(
os.path.join(self.my_path, "./http_notary_list.txt"))

notary_file = os.path.join(os.path.dirname(os.path.abspath( __file__ )),
"./http_notary_list.txt")

def test_init(self):
"""Test basic creation of Notaries class"""
notaries = self._load_notaries()
from Perspectives import Notaries
notaries = Notaries()
self.assertIsNotNone(notaries)

def test_default_notaries(self):
"""Test default_notaries()"""
from Perspectives import default_notaries
notaries = default_notaries()
self.assertIsNotNone(notaries)
self.assertEqual(len(notaries), 4)
self.assertEqual(len(notaries), 8)
for notary in notaries:
self.assertIsNotNone(notary.hostname)
self.assertIsNotNone(notary.port)
self.assertIsNotNone(notary.public_key)

def test_find_notary(self):
"""Test find_notary()"""
notaries = self._load_notaries()
from Perspectives import NotaryParser
notaries = NotaryParser().parse_file(self.notary_file)
for hostname in [
"cmu.ron.lcs.mit.edu",
"convoke.ron.lcs.mit.edu",
Expand Down
31 changes: 31 additions & 0 deletions unittests/NotaryParser_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env python
"""Unititest for NotaryParser class"""

import os.path
import unittest

class TestNotaryParser(unittest.TestCase):
"""Tests for NotaryParser class"""

notary_file = os.path.join(os.path.dirname(os.path.abspath( __file__ )),
"./http_notary_list.txt")

def test_init(self):
"""Test basic creation of NotaryParser"""
from Perspectives import NotaryParser
parser = NotaryParser()

def test_parse_file(self):
"""Test NotaryParser.parse_file()"""
from Perspectives import NotaryParser
parser = NotaryParser()
notaries = parser.parse_file(self.notary_file)
self.assertIsNotNone(notaries)
self.assertEqual(len(notaries), 4)
for notary in notaries:
self.assertIsNotNone(notary.hostname)
self.assertIsNotNone(notary.port)
self.assertIsNotNone(notary.public_key)

if __name__ == "__main__":
unittest.main()
4 changes: 2 additions & 2 deletions unittests/NotaryResponse_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ def _load_responses(self):

def _load_notaries(self):
"""Load notaries and return Notaries instance"""
from Perspectives import Notaries
return Notaries.from_file(
from Perspectives import NotaryParser
return NotaryParser().parse_file(
os.path.join(self.my_path, "./http_notary_list.txt"))

def test_basic(self):
Expand Down
8 changes: 4 additions & 4 deletions utils/notary-query.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import logging
import sys

from Perspectives import Fingerprint, \
PerspectivesException, Notaries, \
from Perspectives import default_notaries, Fingerprint, \
PerspectivesException, Notaries, NotaryParser, \
Service, ServiceType

def normal_query(notaries, service, output, args):
Expand Down Expand Up @@ -116,10 +116,10 @@ def main(argv=None):

if args.notaries_file:
output.debug("Reading notaries from %s" % args.notaries_file)
notaries = notaries_class.from_file(args.notaries_file)
notaries = NotaryParser().parse_file(args.notaries_file)
else:
output.debug("Using default notaries")
notaries = notaries_class.default()
notaries = default_notaries()
output.debug("Have %d notaries" % len(notaries))

query_func(notaries, service, output, args)
Expand Down

0 comments on commit 747bc7b

Please sign in to comment.