[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[minion-cvs] Base implementation of server descriptor blocks.
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.seul.org:/tmp/cvs-serv8971/lib/mixminion
Modified Files:
BuildMessage.py Config.py Crypto.py ServerInfo.py test.py
Log Message:
Base implementation of server descriptor blocks.
BuildMessage.py: use getPublicKey instead of getModulus.
Config.py:
- Add validation functions for more types: base64, hex, publicKey
- Add 'restricted-format' mode
- Pass file contents to validate function
Crypto.py:
- Expose more functions from _minionlib.
- Fix portability bug; run on Python 2.0 again.
ServerInfo.py:
- Untested ServerInfo implementation
- Add beginnings of ServerInfo generation/keygen functionality
test.py:
- Use FakeServerInfo for testing
- Tests for newly exposed Crypto functionality
- Be more verbose when we hang for DH parameter generation
- Tests for new validation functions (but not _parsePublicKey yet)
Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.9
retrieving revision 1.10
diff -u -d -r1.9 -r1.10
--- BuildMessage.py 1 Jul 2002 18:03:05 -0000 1.9
+++ BuildMessage.py 26 Jul 2002 20:52:17 -0000 1.10
@@ -242,7 +242,7 @@
extHeaders = "".join(subhead.getExtraBlocks())
rest = Crypto.ctr_crypt(extHeaders+header, headerKeys[i])
subhead.digest = Crypto.sha1(rest+junkSeen[i])
- pubkey = Crypto.pk_from_modulus(path[i].getModulus())
+ pubkey = path[i].getPacketKey()
esh = Crypto.pk_encrypt(subhead.pack(), pubkey)
header = esh + rest
Index: Config.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Config.py,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- Config.py 26 Jul 2002 15:47:20 -0000 1.4
+++ Config.py 26 Jul 2002 20:52:17 -0000 1.5
@@ -38,11 +38,14 @@
import os
import re
+import binascii
+import time
from cStringIO import StringIO
import mixminion.Common
from mixminion.Common import MixError, getLog
import mixminion.Packet
+import mixminion.Crypto
#----------------------------------------------------------------------
@@ -187,13 +190,78 @@
raise ConfigError("No match found for command %r" %cmd)
+_allChars = "".join(map(chr, range(256)))
+def _parseBase64(s,_hexmode=0):
+ """Validation function. Converts a base-64 encoded config value into
+ its original. Raises ConfigError on failure."""
+ s = s.translate(_allChars, " \t\v\n")
+ try:
+ if _hexmode:
+ return binascii.a2b_hex(s)
+ else:
+ return binascii.a2b_base64(s)
+ except (TypeError, binascii.Error, binascii.Incomplete), e:
+ raise ConfigError("Invalid Base64 data")
+
+def _parseHex(s):
+ """Validation function. Converts a hex-64 encoded config value into
+ its original. Raises ConfigError on failure."""
+ return _parseBase64(s,1)
+
+def _parsePublicKey(s):
+ """Validate function. Converts a Base-64 encoding of an ASN.1
+ represented RSA public key with modulus 65535 into an RSA
+ object."""
+ asn1 = _parseBase64(s)
+ if len(asn1) > 550:
+ raise ConfigError("Overlong public key")
+ try:
+ key = mixminion.Crypto.pk_decode_public_key(asn1)
+ except mixminion.Crypto.CryptoError:
+ raise ConfigError("Invalid public key")
+ if key.get_public_key()[1] != 65535:
+ raise ConfigError("Invalid exponent on public key")
+ return key
+
+_date_re = re.compile(r"(\d\d)/(\d\d)/(\d\d\d\d)")
+_time_re = re.compile(r"(\d\d)/(\d\d)/(\d\d\d\d) (\d\d):(\d\d):(\d\d)")
+def _parseDate(s,_timeMode=0):
+ """Validation function. Converts from DD/MM/YYYY format to a (long)
+ time value for midnight on that date."""
+ s = s.strip()
+ r = (_date_re, _time_re)[_timeMode]
+ m = r.match(s)
+ if not m:
+ raise ConfigError("Invalid %s %r" % (("date", "time")[_timeMode],s))
+ if _timeMode:
+ dd, MM, yyyy, hh, mm, ss = map(int, m.groups())
+ else:
+ dd, MM, yyyy = map(int, m.groups())
+ hh, mm, ss = 0, 0, 0
+
+ if not ((1 <= dd <= 31) and (1 <= MM <= 12) and
+ (1970 <= yyyy) and (0 <= hh < 24) and
+ (0 <= mm < 60) and (0 <= ss <= 61)):
+ raise ConfigError("Invalid %s %r" % (("date","time")[_timeMode],s))
+
+
+ # we set the DST flag to zero so that subtracting time.timezone always
+ # gives us gmt.
+ return time.mktime((yyyy,MM,dd,hh,mm,ss,0,0,0))-time.timezone
+
+def _parseTime(s):
+ """Validation function. Converts from DD/MM/YYYY HH:MM:SS format
+ to a (float) time value for GMT."""
+ return _parseDate(s,1)
+
#----------------------------------------------------------------------
# Regular expression to match a section header.
_section_re = re.compile(r'\[([^\]]+)\]')
# Regular expression to match the first line of an entry
_entry_re = re.compile(r'([^:= \t]+)(?:\s*[:=]|[ \t])\s*(.*)')
-def _readConfigLine(line):
+_restricted_entry_re = re.compile(r'([^:= \t]+): (.*)')
+def _readConfigLine(line, restrict=0):
"""Helper function. Given a line of a configuration file, return
a (TYPE, VALUE) pair, where TYPE is one of the following:
@@ -220,14 +288,16 @@
elif space:
return "MORE", line
else:
- m = _entry_re.match(line)
+ if restrict:
+ m = _restricted_entry_re.match(line)
+ else:
+ m = _entry_re.match(line)
if not m:
return "ERR", "Bad entry"
return "ENT", (m.group(1), m.group(2))
-def _readConfigFile(file):
- """Helper function. Given an open file object for a configuration
- file, parse it into sections.
+def _readConfigFile(contents, restrict=0):
+ """Helper function. Given the string contents of a configuration
Returns a list of (SECTION-NAME, SECTION) tuples, where each
SECTION is a list of (KEY, VALUE, LINENO) tuples.
@@ -238,9 +308,14 @@
curSection = None
lineno = 0
lastKey = None
- for line in file.readlines():
+
+ fileLines = contents.split("\n")
+ if fileLines[-1] == '':
+ del fileLines[-1]
+
+ for line in fileLines:
lineno += 1
- type, val = _readConfigLine(line)
+ type, val = _readConfigLine(line, restrict)
if type == 'ERR':
raise ConfigError("%s at line %s" % (val, lineno))
elif type == 'SEC':
@@ -253,9 +328,15 @@
curSection.append( [key, val, lineno] )
lastKey = key
elif type == 'MORE':
+ if restrict:
+ raise ConfigError("Continuation not allowed at line %s"%lineno)
if not lastKey:
raise ConfigError("Unexpected indentation at line %s" %lineno)
curSection[-1][1] = "%s %s" % (curSection[-1][1], val)
+ else:
+ assert type is None
+ if restrict:
+ raise ConfigError("Empty line not allowed at line %s"%lineno)
return sections
def _formatEntry(key,val,w=79,ind=4):
@@ -292,6 +373,8 @@
# (ALLOW/REQUIRE/ALLOW*/REQUIRE*,
# parseFn,
# default, ) }
+ # _restrictFormat is 1/0: do we allow full RFC822ness, or do
+ # we insist on a tight data format?
## Validation rules:
# A key without a corresponding entry in _syntax gives an error.
@@ -307,7 +390,7 @@
def __init__(self, fname=None, string=None):
"""Create a new _ConfigFile. If fname is set, read from
- fname. If string is set, parse string."""
+ fname. If string is set, parse string. """
assert fname is None or string is None
self.fname = fname
if fname:
@@ -343,7 +426,8 @@
def __reload(self, file):
"""As in .reload(), but takes an open file object."""
- sections = _readConfigFile(file)
+ fileContents = file.read()
+ sections = _readConfigFile(fileContents, self._restrictFormat)
# These will become self.(_sections,_sectionEntries,_sectionNames)
# if we are successful.
@@ -352,7 +436,7 @@
self_sectionNames = []
sectionEntryLines = {}
- for secName, secEntries in sections:
+ for secName, secEntries in sections:
self_sectionNames.append(secName)
if self_sections.has_key(secName):
@@ -440,13 +524,15 @@
assert v == self_sections[s][k] or v in self_sections[s][k]
# Call our validation hook.
- self.validate(self_sections, self_sectionEntries, sectionEntryLines)
+ self.validate(self_sections, self_sectionEntries, sectionEntryLines,
+ fileContents)
self._sections = self_sections
self._sectionEntries = self_sectionEntries
self._sectionNames = self_sectionNames
- def validate(self, sections, sectionEntries, entryLines):
+ def validate(self, sections, sectionEntries, entryLines,
+ fileContents):
"""Check additional semantic properties of a set of configuration
data before overwriting old data. Subclasses should override."""
pass
@@ -480,6 +566,7 @@
return "".join(lines)
class ClientConfig(_ConfigFile):
+ _restrictFormat = 0
_syntax = {
'Host' : { '__SECTION__' : ('REQUIRE', None, None),
'ShredCommand': ('ALLOW', _parseCommand, None),
@@ -497,11 +584,12 @@
def __init__(self, fname=None, string=None):
_ConfigFile.__init__(self, fname, string)
- def validate(self, sections, entries, lines):
+ def validate(self, sections, entries, lines, contents):
#XXXX Write this
pass
class ServerConfig(_ConfigFile):
+ _restrictFormat = 0
_syntax = {
'Host' : ClientConfig._syntax['Host'],
'Server' : { '__SECTION__' : ('REQUIRE', None, None),
@@ -520,7 +608,7 @@
'MaxSkew' : ('ALLOW', _parseInterval,
"10 minutes",) },
'Incoming/MMTP' : { 'Enabled' : ('REQUIRE', _parseBoolean, "no"),
- 'IP' : ('ALLOW', _parseIP, None),
+ 'IP' : ('ALLOW', _parseIP, None),
'Port' : ('ALLOW', _parseInt, "48099"),
'Allow' : ('ALLOW*', None, None),
'Deny' : ('ALLOW*', None, None) },
@@ -536,26 +624,7 @@
def __init__(self, fname=None, string=None):
_ConfigFile.__init__(self, fname, string)
- def validate(self, sections, entries, lines):
+ def validate(self, sections, entries, lines, contents):
#XXXX write this.
pass
-## _serverDescriptorSyntax = {
-## 'Server' : { 'Descriptor-Version' : 'REQUIRE',
-## 'IP' : 'REQUIRE',
-## 'Nickname' : 'ALLOW',
-## 'Identity' : 'REQUIRE',
-## 'Digest' : 'REQUIRE',
-## 'Signature' : 'REQUIRE',
-## 'Valid-After' : 'REQUIRE',
-## 'Valid-Until' : 'REQUIRE',
-## 'Contact' : 'ALLOW',
-## 'Comments' : 'ALLOW',
-## 'Packet-Key' : 'REQUIRE', },
-## 'Incoming/MMTP' : { 'MMTP-Descriptor-Version' : 'REQUIRE',
-## 'Port' : 'REQUIRE',
-## 'Key-Digest' : 'REQUIRE', },
-## 'Modules/MMTP' : { 'MMTP-Descriptor-Version' : 'REQUIRE',
-## 'Allow' : 'ALLOW*',
-## 'Deny' : 'ALLOW*' }
-## }
Index: Crypto.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Crypto.py,v
retrieving revision 1.9
retrieving revision 1.10
diff -u -d -r1.9 -r1.10
--- Crypto.py 26 Jul 2002 15:47:20 -0000 1.9
+++ Crypto.py 26 Jul 2002 20:52:17 -0000 1.10
@@ -18,7 +18,8 @@
__all__ = [ 'CryptoError', 'init_crypto', 'sha1', 'ctr_crypt', 'prng',
'strxor', 'lioness_encrypt', 'lioness_decrypt', 'trng',
- 'pk_encrypt', 'pk_decrypt', 'pk_generate', 'openssl_seed',
+ 'pk_encrypt', 'pk_decrypt', 'pk_sign', 'pk_check_signature',
+ 'pk_generate', 'openssl_seed',
'pk_get_modulus', 'pk_from_modulus',
'pk_encode_private_key', 'pk_decode_private_key',
'Keyset', 'AESCounterPRNG', 'HEADER_SECRET_MODE',
@@ -27,6 +28,8 @@
'HIDE_HEADER_MODE' ]
CryptoError = _ml.CryptoError
+generate_cert = _ml.generate_cert
+PEM_read_key = _ml.rsa_PEM_read_key
# Number of bytes in an AES key.
AES_KEY_LEN = 128 >> 3
@@ -133,6 +136,12 @@
# public key encrypt
return key.crypt(data, 1, 1)
+def pk_sign(data, key):
+ """XXXX"""
+ bytes = key.get_modulus_bytes()
+ data = add_oaep(data,OAEP_PARAMETER,bytes)
+ return key.crypt(data, 0, 1)
+
def pk_decrypt(data,key):
"""Returns the unpadded RSA decryption of data, using the private key in\n
key
@@ -142,6 +151,13 @@
data = key.crypt(data, 0, 0)
return check_oaep(data,OAEP_PARAMETER,bytes)
+def pk_check_signature(data, key):
+ """XXXX"""
+ bytes = key.get_modulus_bytes()
+ # private key decrypt
+ data = key.crypt(data, 1, 0)
+ return check_oaep(data,OAEP_PARAMETER,bytes)
+
def pk_generate(bits=1024,e=65535):
"""Generate a new RSA keypair with 'bits' bits and exponent 'e'. It is
safe to use the default value of 'e'.
@@ -164,6 +180,14 @@
"""Reads an ASN1 representation of a keypair from external storage."""
return _ml.rsa_decode_key(s,0)
+def pk_encode_public_key(key):
+ """Creates an ASN1 representation of a public key for external storage."""
+ return key.encode_key(1)
+
+def pk_decode_public_key(s):
+ """Reads an ASN1 representation of a public key from external storage."""
+ return _ml.rsa_decode_key(s,1)
+
#----------------------------------------------------------------------
# OAEP Functionality
#
@@ -423,7 +447,7 @@
file = None
else:
st = os.stat(file)
- if not (st.st_mode & stat.S_IFCHR):
+ if not (st[stat.ST_MODE] & stat.S_IFCHR):
getLog().error("Entropy source %s isn't a character device", file)
file = None
Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- ServerInfo.py 24 Jun 2002 20:28:19 -0000 1.5
+++ ServerInfo.py 26 Jul 2002 20:52:17 -0000 1.6
@@ -6,33 +6,296 @@
Data structures to represent a server's information, and functions to
martial and unmarshal it.
- ???? Since we don't have an interchange format yet, we only have
- an object with the minimal info."""
+ """
__all__ = [ 'ServerInfo' ]
+import time
+
from mixminion.Modules import SWAP_FWD_TYPE, FWD_TYPE
from mixminion.Packet import IPV4Info
+import mixminion.Config
+import mixminion.Crypto
-#
-# Stub class till we have the real thing
-#
-class ServerInfo:
- """Represents a Mixminion server, and the information needed to send
- messages to it."""
- def __init__(self, addr, port, modulus, keyid):
- self.addr = addr
- self.port = port
- self.modulus = modulus
- self.keyid = keyid
+ConfigError = mixminion.Config.ConfigError
- def getAddr(self): return self.addr
- def getPort(self): return self.port
- def getModulus(self): return self.modulus
- def getKeyID(self): return self.keyid
+# tmp variable to make this easier to spell.
+C = mixminion.Config
+
+MAX_NICKNAME = 128
+MAX_CONTACT = 256
+MAX_COMMENT = 1024
+MIN_IDENTITY_BYTES = 2048 >> 3
+MAX_IDENTITY_BYTES = 4096 >> 3
+PACKET_KEY_BYTES = 1024 >> 3
+
+class ServerInfo(mixminion.Config._ConfigFile):
+ _restrictFormat = 1
+ _syntax = {
+ "Server" : { "__SECTION__": ("REQUIRE", None, None),
+ "IP": ("REQUIRE", C._parseIP, None),
+ "Nickname": ("REQUIRE", None, None),
+ "Identity": ("REQUIRE", C._parsePublicKey, None),
+ "Digest": ("REQUIRE", C._parseBase64, None),
+ "Signature": ("REQUIRE", C._parseBase64, None),
+ "Published": ("REQUIRE", C._parseTime, None),
+ "Valid-After": ("REQUIRE", C._parseDate, None),
+ "Valid-Until": ("REQUIRE", C._parseDate, None),
+ "Contact": ("ALLOW", None, None),
+ "Comments": ("ALLOW", None, None),
+ "Packet-Key": ("REQUIRE", C._parsePublicKey, None),
+ },
+ "Incoming/MMTP" : {
+ "Version": ("REQUIRE", None, None),
+ "Port": ("REQUIRE", C._parseInt, None),
+ "Key-Digest": ("REQUIRE", C._parseBase64, None),
+ "Protocols": ("REQUIRE", None, None),
+ },
+ "Modules/MMTP" : {
+ "Version": ("REQUIRE", None, None),
+ "Protocols": ("REQUIRE", None, None),
+ },
+ "Modules/MBOX" : {
+ "Version": ("REQUIRE", None, None),
+ },
+ "Modules/SMTP" : {
+ "Version": ("REQUIRE", None, None),
+ }
+ }
+
+ def __init__(self, fname, string):
+ mixminion.Config._ConfigFile.__init__(self, fname, string)
+
+ def validate(self, sections, entries, lines, contents):
+ ####
+ # Check 'Server' section.
+
+ server = sections['Server']
+ if server['Descriptor-Version'] != '1.0':
+ raise ConfigError("Unrecognized descriptor version")
+ if len(server['Nickname']) > MAX_NICKNAME:
+ raise ConfigError("Nickname too long")
+ identityKey = server['Identity-Key']
+ identityBytes = identityKey.get_modulus_bytes()
+ if not (MIN_IDENTITY_BYTES <= identityBytes <= MAX_IDENTITY_BYTES):
+ raise ConfigError("Invalid length on identity key")
+ if server['Valid-Until'] <= server['Valid-After']:
+ raise ConfigError("Server is never valid")
+ if len(server['Contact']) > MAX_CONTACT:
+ raise ConfigError("Contact too long")
+ if len(sever['Comments']) > MAX_COMMENTS:
+ raise ConfigError("Comments too long")
+ packetKeyBytes = server['Packet-Key'].get_modulus_bytes()
+ if packetKeyBytes != PACKET_KEY_BYTES:
+ raise ConfigError("Invalid length on packet key")
+
+ ####
+ # Check Digest of file
+ digest = getServerInfoDigest(contents)
+ if digest != server['Digest']:
+ raise ConfigError("Invalid digest")
+
+ signature = server['']
+ if digest != mixminion.Crypto.pk_check_signature(server['Signature'],
+ identityKey):
+ raise ConfigError("Invalid signature")
+
+ #### XXXX CHECK OTHER SECTIONS
+
+ def getAddr(self):
+ return self['Server']['IP']
+ def getPort(self):
+ return self['Incoming/MMTP']['Port']
+
+ def getPacketKey(self):
+ return self['Server']['Packet-Key']
+
+ def getKeyID(self):
+ return self['Incoming/MMTP']['Key-Digest']
+
def getRoutingInfo(self):
"""Returns a mixminion.Packet.IPV4Info object for routing messages
to this server."""
- return IPV4Info(self.addr, self.port, self.keyid)
+ return IPV4Info(self.getAddr(), self.getPort(), self.getKeyID())
+#----------------------------------------------------------------------
+# This should go in a different file.
+class ServerKeys:
+ "XXXX"
+ def __init__(self, keyroot, keyname, hashroot):
+ self.keydir = os.path.join(keyroot, "key_"+keyname)
+ self.hashlogFile = os.path.join(hashroot, "hash_"+keyname)
+ self.packetKeyFile = os.path.join(keydir, "mix.key")
+ self.mmtpKeyFile = os.path.join(keydir, "mmtp.key")
+ self.certFile = os.path.join(keydir, "mmtp.cert")
+
+ def load(self, password=None):
+ r = mixminion.Crypto.PEM_read_key
+ if password:
+ self.packetKey = r(self.packetKeyFile,0,password)
+ self.mmtpKey = r(self.mmtpKeyFile,0,password)
+ else:
+ self.packetKey = r(self.packetKeyFile,0)
+ self.mmtpKey = r(self.mmtpKeyFile,0)
+
+ def save(self, pasword=None):
+ if password:
+ self.packetKey.PEM_write_key(self.packetKeyFile,0,password)
+ self.mmtpKey.PEM_write_key(self.mmtpKeyFile,0,password)
+ else:
+ self.packetKey.PEM_write_key(self.packetKeyFile,0)
+ self.mmtpKey.PEM_write_key(self.mmtpKeyFile,0)
+
+ def getCertFileName(self): return self.certFile
+ def getHashLogFileName(self): return self.hashlogFile
+ def getPacketKey(self): return self.packetKey
+ def getMMTPKey(self): return self.mmtpKey
+ def getMMTPKeyID(self):
+ return sha1(self.mmtpKey.encode_key(1))
+
+def _base64(s):
+ return binascii.b2a_base64(s).replace("\n","")
+
+def _time(t):
+ gmt = time.gmtime(t)
+ return "%02d/%02s/%04d %02d:%02d:%02d" % (
+ gmt[2],gmt[1],gmt[0], gmt[3],gmt[4],gmt[5])
+
+def _date(t):
+ gmt = time.gmtime(t+1)
+ return "%02d/%02s/%04d" % (gmt[2],gmt[1],gmt[0])
+
+def generateNewServerInfoAndKeys(config, identityKey, keydir, keyname):
+ packetKey = mixminion.Crypto.pk_generate(PACKET_KEY_BYTES*8)
+ mmtpKey = mixminion.Crypto.pk_generate(PACKET_KEY_BYTES*8)
+
+ serverKeys = ServerKeys(keydir, keyname)
+ serverKeys.packetKey = packetKey
+ serverKeys.mmtpKey = mmtpKey
+ serverKeys.save()
+
+ nickname = "XXXX" #XXXX"
+ contact = "XXXX"
+ comment = "XXXX"
+ validAt = time.time() #XXXX
+ validUntil = time.time()+365*24*60*60 #XXXX
+ lifespan = ceilDiv(validUntil-validAt , 24*60*60)#XXXX
+
+ mixminion.Crypto.generate_cert(serverKeys.getCertFileName(),
+ mmtpKey,
+ lifespan,
+ "MMTP certificate for %s" %nickname)
+
+ if not config['Server']['Incoming/MMTP']:
+ # Don't generate a serverInfo if we don't allow connections in.
+ return
+
+ fields = {
+ "IP": config['Incoming/MMTP']['IP'],
+ "Port": config['Incoming/MMTP']['Port'],
+ "Nickname": nickname,
+ "Identity":
+ _base64(mixminion.Crypto.pk_encode_public_key(identityKey)),
+ "Published": _time(time.time()),
+ "ValidAfter": _date(validAt),
+ "ValidUntil": _date(validUntil),
+ "PacketKey":
+ _base64(mixminion.Crypto.pk_encode_public_key(publicKey)),
+ "KeyID":
+ _base64(serverKeys.getMMTPKeyID()),
+ }
+
+ info = """\
+ [Server]
+ Descriptor-Version: 1.0
+ IP: %(IP)s
+ Port: %(Port)s
+ Identity: %(Identity)s
+ Digest:
+ Signature:
+ Published: %(Published)s
+ Valid-After: %(ValidAfter)s
+ Valid-Until: %(ValidUntil)s
+ Packet-Key: %(PacketKey)s
+ """ %(fields)
+ if contact:
+ info += "Contact %s\n"%contact
+ if comment:
+ info += "Contact %s\n"%comment
+
+ if ALLOW_INCOMING_MMTP: #XXXX
+ info += """\
+ [Incoming/MMTP]
+ Version: 1.0
+ Port: %(Port)s
+ Key-Digest: %(KeyID)s
+ Protocols: 1.0
+ """
+ if ALLOW_OUTGOING_MMTP: #XXXX
+ info += """\
+ [Modules/MMTP]
+ Version: 1.0
+ Protocols: 1.0
+ """
+ for k,v in config.getSectionItems("Outgoing/MMTP"):
+ # XXXX write the rule
+ pass
+ if ALLOW_DELIVERY_MBOX: #XXXX
+ info += """\
+ [Modules/MBOX]
+ Version: 1.0
+ """
+
+ # Remove extra (leading) whitespace.
+ lines = [ line.strip() for line in info.split("\n") ]
+ # Remove empty lines
+ lines = filter(None, lines)
+ info = "\n".join(lines)
+ info = signServerInfo(info, identityKey)
+
+ # debug XXXX
+ ServerInfo(string=info)
+
+ return info
+
+
+#----------------------------------------------------------------------
+def getServerInfoDigest(info):
+ return _getServerInfoDiggestImpl(info, None)
+
+def signServerInfo(info, rsa):
+ return _getServerInfoDiggestImpl(info, rsa)
+
+def _getServerInfoDigestImpl(info, rsa=None):
+ infoLines = info.split("\n")
+ if not infoLines[0] == "[Server]":
+ raise ConfigError("Must begin with server section")
+ digestLine = None
+ signatureLine = None
+ infoLines = info.split("\n")
+ for lineno in range(len(infoLines)):
+ line = infoLines[lineNo]
+ if line.startswith("Digest:") and digestLine is None:
+ digestLine = lineNo
+ elif line.startswith("Signature:") and signatureLine is None:
+ signatureLine = lineNo
+
+ assert digestLine is not None and signatureLine is not None
+
+ infoLines[digestLine] = 'Digest:'
+ infoLines[signatureLine] = 'Signature:'
+ info = "\n".join(infoLines)
+
+ digest = mixminion.Crypto.sha1(info)
+ if pk is None:
+ return digest
+
+ #### Signature case.
+ signature = mixminion.Crypto.pk_sign(digest,rsa)
+ digest = _base64(digest)
+ signature = binascii.b2a_base64(signature).replace("\n","")
+ infoLines[digestLine] = 'Digest: '+digest
+ infoLines[signatureLine] = 'Signature: '+signature
+
+ return "\n".join(infoLines)
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -d -r1.13 -r1.14
--- test.py 26 Jul 2002 15:47:20 -0000 1.13
+++ test.py 26 Jul 2002 20:52:17 -0000 1.14
@@ -322,6 +322,16 @@
k512.crypt(pk_encrypt(msg,k512), 0, 0),
mixminion.Crypto.OAEP_PARAMETER, 64))
+ # test signing
+ eq(pk_check_signature(pk_sign(msg, k1024),pub1024), msg)
+ eq(pk_check_signature(pk_sign(msg, k1024),k1024), msg)
+ self.failUnlessRaises(TypeError,
+ pk_sign, msg, pub1024)
+ self.failUnlessRaises(CryptoError,
+ pk_check_signature,
+ pk_sign(msg, k1024)+"X",
+ pub1024)
+
# Make sure we can still encrypt after we've encoded/decoded a
# key.
encoded = pk_encode_private_key(k512)
@@ -616,19 +626,34 @@
pk2 = pk_generate()
pk3 = pk_generate()
-from mixminion.ServerInfo import ServerInfo
+
+class FakeServerInfo:
+ """Represents a Mixminion server, and the information needed to send
+ messages to it."""
+ def __init__(self, addr, port, key, keyid):
+ self.addr = addr
+ self.port = port
+ self.key = key
+ self.keyid = keyid
+
+ def getAddr(self): return self.addr
+ def getPort(self): return self.port
+ def getPacketKey(self): return self.key
+ def getKeyID(self): return self.keyid
+
+ def getRoutingInfo(self):
+ """Returns a mixminion.Packet.IPV4Info object for routing messages
+ to this server."""
+ return IPV4Info(self.addr, self.port, self.keyid)
class BuildMessageTests(unittest.TestCase):
def setUp(self):
self.pk1 = BMTSupport.pk1
self.pk2 = BMTSupport.pk2
self.pk3 = BMTSupport.pk3
- n_1 = pk_get_modulus(self.pk1)
- n_2 = pk_get_modulus(self.pk2)
- n_3 = pk_get_modulus(self.pk3)
- self.server1 = ServerInfo("127.0.0.1", 1, n_1, "X"*20)
- self.server2 = ServerInfo("127.0.0.2", 3, n_2, "Z"*20)
- self.server3 = ServerInfo("127.0.0.3", 5, n_3, "Q"*20)
+ self.server1 = FakeServerInfo("127.0.0.1", 1, self.pk1, "X"*20)
+ self.server2 = FakeServerInfo("127.0.0.2", 3, self.pk2, "Z"*20)
+ self.server3 = FakeServerInfo("127.0.0.3", 5, self.pk3, "Q"*20)
def test_buildheader_1hop(self):
bhead = BuildMessage._buildHeader
@@ -747,7 +772,7 @@
def getLongRoutingInfo(longStr2=longStr2):
return LocalInfo("fred",longStr2)
- server4 = ServerInfo("127.0.0.1", 1, pk_get_modulus(self.pk1), "X"*20)
+ server4 = FakeServerInfo("127.0.0.1", 1, self.pk1, "X"*20)
server4.getRoutingInfo = getLongRoutingInfo
secrets.append("1"*16)
@@ -966,12 +991,10 @@
self.tmpfile = mktemp(".db")
unlink_db_on_exit(self.tmpfile)
h = self.hlog = HashLog(self.tmpfile, "Z"*20)
- n_1 = pk_get_modulus(self.pk1)
- n_2 = pk_get_modulus(self.pk2)
- n_3 = pk_get_modulus(self.pk3)
- self.server1 = ServerInfo("127.0.0.1", 1, n_1, "X"*20)
- self.server2 = ServerInfo("127.0.0.2", 3, n_2, "Z"*20)
- self.server3 = ServerInfo("127.0.0.3", 5, n_3, "Q"*20)
+
+ self.server1 = FakeServerInfo("127.0.0.1", 1, self.pk1, "X"*20)
+ self.server2 = FakeServerInfo("127.0.0.2", 3, self.pk2, "Z"*20)
+ self.server3 = FakeServerInfo("127.0.0.3", 5, self.pk3, "Q"*20)
self.sp1 = PacketHandler(self.pk1, h)
self.sp2 = PacketHandler(self.pk2, h)
self.sp3 = PacketHandler(self.pk3, h)
@@ -1065,7 +1088,7 @@
from mixminion.PacketHandler import ContentError
# A long intermediate header needs to fail.
- server1X = ServerInfo("127.0.0.1", 1, pk_get_modulus(self.pk1), "X"*20)
+ server1X = FakeServerInfo("127.0.0.1", 1, self.pk1, "X"*20)
class _packable:
def pack(self): return "x"*200
server1X.getRoutingInfo = lambda _packable=_packable: _packable()
@@ -1347,10 +1370,16 @@
if dh_fname:
dhfile = dh_fname
if not os.path.exists(dh_fname):
- _ml.generate_dh_parameters(dhfile, 0)
+ print "[Generating DH parameters...",
+ sys.stdout.flush()
+ _ml.generate_dh_parameters(dhfile, 0)
+ print "done.]"
else:
+ print "[Generating DH parameters (not caching)...",
+ sys.stdout.flush()
_ml.generate_dh_parameters(dhfile, 0)
unlink_on_exit(dhfile)
+ print "done.]"
pk = _ml.rsa_generate(1024, 65535)
pk.PEM_write_key(open(pkfile, 'w'), 0)
_ml.generate_cert(certfile, pk, 365, "Testing certificate")
@@ -1492,6 +1521,7 @@
from mixminion.Config import _ConfigFile, ConfigError, _parseInt
class TestConfigFile(_ConfigFile):
+ _restrictFormat = 0
_syntax = { 'Sec1' : {'__SECTION__': ('REQUIRE', None, None),
'Foo': ('REQUIRE', None, None),
'Bar': ('ALLOW', None, "default"),
@@ -1644,6 +1674,14 @@
self.assertEquals(C._parseCommand("rm"), ("/bin/rm", []))
self.assertEquals(C._parseCommand("/bin/ls"), ("/bin/ls", []))
self.failUnless(C._parseCommand("python")[0] is not None)
+ self.assertEquals(C._parseBase64(" YW\nJj"), "abc")
+ self.assertEquals(C._parseHex(" C0D0"), "\xC0\xD0")
+ tm = C._parseDate("30/05/2002")
+ self.assertEquals(time.gmtime(tm)[:6], (2002,5,30,0,0,0))
+ tm = C._parseDate("01/01/2000")
+ self.assertEquals(time.gmtime(tm)[:6], (2000,1,1,0,0,0))
+ tm = C._parseTime("25/12/2001 06:15:10")
+ self.assertEquals(time.gmtime(tm)[:6], (2001,12,25,6,15,10))
def fails(fn, val, self=self):
self.failUnlessRaises(ConfigError, fn, val)
@@ -1662,6 +1700,15 @@
fails(C._parseIP, "192.0.0")
fails(C._parseIP, "192.0.0.0.0")
fails(C._parseIP, "A.0.0.0")
+ fails(C._parseBase64, "Y")
+ fails(C._parseHex, "Z")
+ fails(C._parseHex, "A")
+ fails(C._parseDate, "1/1/2000")
+ fails(C._parseDate, "01/50/2000")
+ fails(C._parseDate, "01/50/2000 12:12:12")
+ fails(C._parseTime, "01/50/2000 12:12:12")
+ fails(C._parseTime, "01/50/2000 12:12:99")
+
nonexistcmd = '/file/that/does/not/exist'
if not os.path.exists(nonexistcmd):
fails(C._parseCommand, nonexistcmd)