From ce277674b98c0e75f5eecd95fe6ff052607a0c22 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 14 Oct 2008 18:42:57 +0000 Subject: Add some in-code documentation, fix some dead code, add a license, add a working distutils script. git-svn-id: file:///home/or/svnrepo/updater/trunk@17092 55e972cd-5a19-0410-ae62-a4d7a52db4cd --- LICENSE | 40 +++++++++++++++ Makefile | 9 ++-- lib/thandy/ClientCLI.py | 1 + lib/thandy/ServerCLI.py | 1 + lib/thandy/SignerCLI.py | 1 + lib/thandy/__init__.py | 1 + lib/thandy/checkJson.py | 1 + lib/thandy/download.py | 2 +- lib/thandy/formats.py | 1 + lib/thandy/keys.py | 46 +++++++++++------ lib/thandy/master_keys.py | 2 +- lib/thandy/repository.py | 1 + lib/thandy/tests.py | 1 + lib/thandy/util.py | 1 + setup.py | 124 ++++++++++++++++++++++++++++++++++++++++++++++ 15 files changed, 213 insertions(+), 19 deletions(-) create mode 100644 LICENSE create mode 100644 setup.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8cf439a --- /dev/null +++ b/LICENSE @@ -0,0 +1,40 @@ + This file contains the license for Thandy, + a free software project to securely fetch and install software updates. + + If you got this file as a part of a larger bundle, + there may be other license terms that you should be aware of. + + +=============================================================================== +Thandy is distributed under this license: + +Copyright (c) 2008, The Tor Project, Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + + * Neither the names of the copyright owners nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +=============================================================================== diff --git a/Makefile b/Makefile index c67b8b9..aa5d6c0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ -export PYTHONPATH=./lib +all: + python setup.py build test: - #python -m sexp.tests - python -m thandy.tests + export PYTHONPATH=./lib && python -m thandy.tests + +install: + python setup.py install \ No newline at end of file diff --git a/lib/thandy/ClientCLI.py b/lib/thandy/ClientCLI.py index 702ebbf..ba7fb2e 100644 --- a/lib/thandy/ClientCLI.py +++ b/lib/thandy/ClientCLI.py @@ -1,3 +1,4 @@ +# Copyright 2008 The Tor Project, Inc. See LICENSE for licensing information. import os import sys diff --git a/lib/thandy/ServerCLI.py b/lib/thandy/ServerCLI.py index 47fa912..2090ed9 100644 --- a/lib/thandy/ServerCLI.py +++ b/lib/thandy/ServerCLI.py @@ -1,3 +1,4 @@ +# Copyright 2008 The Tor Project, Inc. See LICENSE for licensing information. import os import sys diff --git a/lib/thandy/SignerCLI.py b/lib/thandy/SignerCLI.py index 4facc2e..d3af5fb 100644 --- a/lib/thandy/SignerCLI.py +++ b/lib/thandy/SignerCLI.py @@ -1,3 +1,4 @@ +# Copyright 2008 The Tor Project, Inc. See LICENSE for licensing information. import os import getopt diff --git a/lib/thandy/__init__.py b/lib/thandy/__init__.py index 87fd983..03b262e 100644 --- a/lib/thandy/__init__.py +++ b/lib/thandy/__init__.py @@ -1,3 +1,4 @@ +# Copyright 2008 The Tor Project, Inc. See LICENSE for licensing information. __all__ = [ 'formats' ] diff --git a/lib/thandy/checkJson.py b/lib/thandy/checkJson.py index e6b6c6e..b5fef3d 100644 --- a/lib/thandy/checkJson.py +++ b/lib/thandy/checkJson.py @@ -1,3 +1,4 @@ +# Copyright 2008 The Tor Project, Inc. See LICENSE for licensing information. import re import sys diff --git a/lib/thandy/download.py b/lib/thandy/download.py index 0b22cfa..bf7dc43 100644 --- a/lib/thandy/download.py +++ b/lib/thandy/download.py @@ -1,4 +1,4 @@ - +# Copyright 2008 The Tor Project, Inc. See LICENSE for licensing information. import urllib2 import httplib diff --git a/lib/thandy/formats.py b/lib/thandy/formats.py index 7db1d8f..e10f0aa 100644 --- a/lib/thandy/formats.py +++ b/lib/thandy/formats.py @@ -1,3 +1,4 @@ +# Copyright 2008 The Tor Project, Inc. See LICENSE for licensing information. import simplejson import time diff --git a/lib/thandy/keys.py b/lib/thandy/keys.py index 5b4e072..2f433b5 100644 --- a/lib/thandy/keys.py +++ b/lib/thandy/keys.py @@ -1,3 +1,4 @@ +# Copyright 2008 The Tor Project, Inc. See LICENSE for licensing information. # These require PyCrypto. import Crypto.PublicKey.RSA @@ -17,9 +18,8 @@ import thandy.formats import thandy.util class PublicKey: + """Abstract base class for public keys.""" def __init__(self): - # Confusingly, these roles are the ones used for a private key to - # remember what we're willing to do with it. self._roles = [] def format(self): raise NotImplemented() @@ -32,13 +32,25 @@ class PublicKey: def getKeyID(self): raise NotImplemented() def getRoles(self): + """Remove a list of all roles supported by this key. A role is + from this key. A role is a doctype,pathPattern tuple. + """ return self._roles def addRole(self, role, path): + """Add a role to the list of roles supported by this key. + A role is a permission to sign a given kind of document + (one of thandy.format.ALL_ROLES) at a given set of relative + paths. + """ assert role in thandy.formats.ALL_ROLES self._roles.append((role, path)) def clearRoles(self): + """Remove all roles from this key.""" del self._roles[:] def hasRole(self, role, path): + """Return true iff this key has a role that allows it to sign + a document of type 'role' at location in the repository 'path'. + """ for r, p in self._roles: if r == role and thandy.formats.rolePathMatches(p, path): return True @@ -57,6 +69,7 @@ if hex(1L).upper() == "0X1L": return binascii.a2b_hex(h) elif hex(1L).upper() == "0X1": def intToBinary(number): + "Variant for future versions of pythons that don't append 'L'." h = hex(long(number)) h = h[2:] if len(h)%2: @@ -73,9 +86,11 @@ def binaryToInt(binary): return long(binascii.b2a_hex(binary), 16) def intToBase64(number): + """Convert an int or long to a big-endian base64-encoded value.""" return thandy.formats.formatBase64(intToBinary(number)) def base64ToInt(number): + """Convert a big-endian base64-encoded value to a long.""" return binaryToInt(thandy.formats.parseBase64(number)) def _pkcs1_padding(m, size): @@ -123,11 +138,14 @@ class RSAKey(PublicKey): @staticmethod def generate(bits=2048): + """Generate a new RSA key, with modulus length 'bits'.""" key = Crypto.PublicKey.RSA.generate(bits=bits, randfunc=os.urandom) return RSAKey(key) @staticmethod def fromJSon(obj): + """Construct an RSA key from the output of the format() method. + """ # obj must match RSAKEY_SCHEMA thandy.formats.RSAKEY_SCHEMA.checkMatch(obj) @@ -150,9 +168,14 @@ class RSAKey(PublicKey): return result def isPrivateKey(self): + """Return true iff this key has private-key components""" return hasattr(self.key, 'd') def format(self, private=False, includeRoles=False): + """Returna a new object to represent this key in json format. + If 'private', include private-key data. If 'includeRoles', + include role information. + """ n = intToBase64(self.key.n) e = intToBase64(self.key.e) result = { '_keytype' : 'rsa', @@ -168,25 +191,19 @@ class RSAKey(PublicKey): return result def getKeyID(self): + """Return the KeyID for this key. + """ if self.keyid == None: d_obj = Crypto.Hash.SHA256.new() thandy.formats.getDigest(self.format(), d_obj) self.keyid = thandy.formats.formatHash(d_obj.digest()) return self.keyid - def _digest(self, obj, method=None): - if method in (None, "sha256-pkcs1"): - d_obj = Crypto.Hash.SHA256.new() - thandy.formats.getDigest(obj, d_obj) - digest = d_obj.digest() - return ("sha256-pkcs1", digest) - - raise UnknownMethod(method) - def sign(self, obj=None, digest=None): assert _xor(obj == None, digest == None) + method = "sha256-pkcs1" if digest == None: - method, digest = self._digest(obj) + digest = thandy.formats.getDigest(obj) m = _pkcs1_padding(digest, (self.key.size()+1) // 8) sig = intToBase64(self.key.sign(m, "")[0]) return (method, sig) @@ -194,9 +211,9 @@ class RSAKey(PublicKey): def checkSignature(self, method, sig, obj=None, digest=None): assert _xor(obj == None, digest == None) if method != "sha256-pkcs1": - raise UnknownMethod("method") + raise UnknownMethod(method) if digest == None: - method, digest = self._digest(obj, method) + digest = thandy.formats.getDigest(obj) sig = base64ToInt(sig) m = _pkcs1_padding(digest, (self.key.size()+1) // 8) return bool(self.key.verify(m, (sig,))) @@ -324,6 +341,7 @@ def decryptSecret(encrypted, password): return secret class KeyStore(thandy.formats.KeyDB): + """Helper to store private keys in an encrypted file.""" def __init__(self, fname, encrypted=True): thandy.formats.KeyDB.__init__(self) diff --git a/lib/thandy/master_keys.py b/lib/thandy/master_keys.py index 0d455d1..4818d93 100644 --- a/lib/thandy/master_keys.py +++ b/lib/thandy/master_keys.py @@ -1,4 +1,4 @@ - +# Copyright 2008 The Tor Project, Inc. See LICENSE for licensing information. MASTER_KEYS = [ diff --git a/lib/thandy/repository.py b/lib/thandy/repository.py index dc13f1b..32108c0 100644 --- a/lib/thandy/repository.py +++ b/lib/thandy/repository.py @@ -1,3 +1,4 @@ +# Copyright 2008 The Tor Project, Inc. See LICENSE for licensing information. import thandy.formats import thandy.util diff --git a/lib/thandy/tests.py b/lib/thandy/tests.py index 8b967a5..e2fb767 100644 --- a/lib/thandy/tests.py +++ b/lib/thandy/tests.py @@ -1,3 +1,4 @@ +# Copyright 2008 The Tor Project, Inc. See LICENSE for licensing information. import unittest import doctest diff --git a/lib/thandy/util.py b/lib/thandy/util.py index e87ed8b..2b1c572 100644 --- a/lib/thandy/util.py +++ b/lib/thandy/util.py @@ -1,3 +1,4 @@ +# Copyright 2008 The Tor Project, Inc. See LICENSE for licensing information. import os import sys diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..9fd6680 --- /dev/null +++ b/setup.py @@ -0,0 +1,124 @@ +#!/usr/bin/python +# Copyright 2008 The Tor Project. See LICENSE for licensing information. +# $Id: setup.py,v 1.103 2007-09-15 19:06:37 nickm Exp $ + +import sys + +# +# Current Thandy version +# +VERSION = '0.0.1-alpha' +# System: 0==alpha, 50==beta, 98=pre, 99==release candidate, 100==release +VERSION_INFO = (0,0,1) + +for name in [ "simplejson", "Crypto" ]: + try: + __import__(name) + except ImportError: + print "Missing support for module %s"%name + sys.exit(1) + +import os, re, shutil, string, struct, sys + +os.umask(022) + +#====================================================================== +# Create startup scripts if we're installing. + +if not os.path.isdir("./bin"): + os.mkdir("./bin") + +SCRIPTS = [] + +def makescripts(extrapath=None): + del SCRIPTS[:] + for script_suffix, modname in [ ("server", "ServerCLI"), + ("client", "ClientCLI"), + ("pk", "SignerCLI"), ]: + fname = os.path.join("./bin", "thandy-%s"%script_suffix) + if sys.platform == "win32": + fname += ".py" + f = open(fname, 'w') + f.write("#!/bin/sh\n") + if extrapath: + f.write('PYTHONPATH="$PYTHONPATH:%s"\n'%extrapath) + f.write('export PYTHONPATH\n') + f.write("%s -m thandy.%s $*\n" %(sys.executable, modname)) + f.close() + SCRIPTS.append(fname) + +#====================================================================== +# Define a helper to let us run commands from the compiled code. +def _haveCmd(cmdname): + for entry in os.environ.get("PATH", "").split(os.pathsep): + if os.path.exists(os.path.join(entry, cmdname)): + return 1 + return 0 + +def requirePythonDev(e=None): + if os.path.exists("/etc/debian_version"): + v = sys.version[:3] + print "Debian may expect you to install python%s-dev"%v + elif os.path.exists("/etc/redhat-release"): + print "Redhat may expect you to install python2-devel" + else: + print "You may be missing some 'python development' package for your" + print "distribution." + + if e: + print "(Error was: %s)"%e + + sys.exit(1) + +try: + from distutils.core import Command + from distutils.errors import DistutilsPlatformError + from distutils.sysconfig import get_makefile_filename +except ImportError, e: + print "\nUh oh. You have Python installed, but I didn't find the distutils" + print "module, which is supposed to come with the standard library.\n" + + requirePythonDev() + +try: + # This catches failures to install python2-dev on some redhats. + get_makefile_filename() +except IOError: + print "\nUh oh. You have Python installed, but distutils can't find the" + print "Makefile it needs to build additional Python components.\n" + + requirePythonDev() + +#====================================================================== +# Now, tell setup.py how to cope. +import distutils.core, distutils.command.install +from distutils.core import setup, Distribution + +class InstallCommand(distutils.command.install.install): + def run(self): + script_path = None + sys_path = map(os.path.normpath, sys.path) + sys_path = map(os.path.normcase, sys_path) + install_lib = os.path.normcase(os.path.normpath(self.install_lib)) + + if install_lib not in sys_path: + script_path = install_lib + + makescripts(self.install_lib) + + distutils.command.install.install.run(self) + +setup(name='Thandy', + version=VERSION, + license="3-clause BSD", + description= + "Thandy: Secure cross-platform update automation tool.", + author="Nick Mathewson", + author_email="nickm@freehaven.net", + url="http://www.torproject/org", + package_dir={ '' : 'lib' }, + packages=['thandy'], + scripts=SCRIPTS, + cmdclass={'install': InstallCommand}, +) + -- cgit v1.2.3