[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[minion-cvs] More testing, docs: some bulletproofing, some bugfixing
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.seul.org:/tmp/cvs-serv22979/lib/mixminion
Modified Files:
BuildMessage.py Main.py Modules.py Packet.py test.py
Log Message:
More testing, docs: some bulletproofing, some bugfixing
BuildMessage:
- Bugfixing, slight doc improvement.
- Some (not enough) zlib bulletproofing
Main:
- More verboseness on import failure
Modules:
- Start of e2e work for exit modules
Packet:
- Loads of docs; more symbolic consts
test:
- More tests for e2e message encoding
- Cache RSA keys and avoid DH retesting to speed tests by a factor
of 3 to 4.
- Make 'Gravity's Rainbow' example more explicit
aes_ctr.c:
- Remove ancient if-0'd coded
crypt.c:
- Bulletproof rsa_generate to withstandd silly values of n,e.
Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.15
retrieving revision 1.16
diff -u -d -r1.15 -r1.16
--- BuildMessage.py 14 Oct 2002 03:03:42 -0000 1.15
+++ BuildMessage.py 16 Oct 2002 23:12:11 -0000 1.16
@@ -12,13 +12,13 @@
import mixminion.Crypto as Crypto
import mixminion.Modules as Modules
-__all__ = [ 'Address',
+__all__ = [ 'Address',
'buildForwardMessage', 'buildEncryptedMessage', 'buildReplyMessage',
'buildStatelessReplyBlock', 'buildReplyBlock', 'decodePayload',
- 'decodeForwardPayload', 'decodeEncryptedForwardPayload',
+ 'decodeForwardPayload', 'decodeEncryptedForwardPayload',
'decodeReplyPayload', 'decodeStatelessReplyPayload' ]
-def buildForwardMessage(payload, exitType, exitInfo, path1, path2,
+def buildForwardMessage(payload, exitType, exitInfo, path1, path2,
paddingPRNG=None):
"""Construct a forward message.
payload: The payload to deliver. Must compress to under 28K-22b.
@@ -28,17 +28,17 @@
path2: Sequence of ServerInfo objects for the 2nd leg of the path
paddingPRNG
- Note: If either path is empty, the message is vulnerable to tagging
+ Note: If either path is empty, the message is vulnerable to tagging
attacks! (FFFF we should check this.)
"""
if paddingPRNG is None: paddingPRNG = Crypto.AESCounterPRNG()
payload = _encodePayload(payload, 0, paddingPRNG)
tag = _getRandomTag(paddingPRNG)
- exitInfo = tag + exitInfo
+ exitInfo = tag + exitInfo
return _buildMessage(payload, exitType, exitInfo, path1, path2,paddingPRNG)
-def buildEncryptedForwardMessage(payload, exitType, exitInfo, path1, path2,
+def buildEncryptedForwardMessage(payload, exitType, exitInfo, path1, path2,
key, paddingPRNG=None, secretRNG=None):
"""XXXX
"""
@@ -63,7 +63,7 @@
payload = encrypted + lionessPart
tag = payload[:TAG_LEN]
payload = payload[TAG_LEN:]
- exitInfo = tag + exitInfo
+ exitInfo = tag + exitInfo
assert len(payload) == 28*1024
return _buildMessage(payload, exitType, exitInfo, path1, path2,paddingPRNG)
@@ -74,7 +74,7 @@
if paddingPRNG is None: paddingPRNG = Crypto.AESCounterPRNG()
payload = _encodePayload(payload, 0, paddingPRNG)
-
+
# XXXX Document this mode
k = Crypto.Keyset(replyBlock.encryptionKey).getLionessKeys(
Crypto.PAYLOAD_ENCRYPT_MODE)
@@ -84,11 +84,11 @@
return _buildMessage(payload, None, None,
path1=path1, path2=replyBlock)
-def buildReplyBlock(path, exitType, exitInfo, expiryTime=0, secretPRNG=None,
+def buildReplyBlock(path, exitType, exitInfo, expiryTime=0, secretPRNG=None,
tag=None):
"""Return a 3-tuple containing (1) a newly-constructed reply block, (2)
a list of secrets used to make it, (3) a tag.
-
+
path: A list of ServerInfo
exitType: Routing type to use for the final node
exitInfo: Routing info for the final node, not including tag.
@@ -97,7 +97,7 @@
provided, uses an AES counter-mode stream seeded from our
entropy source. Note: the secrets are generated so that they
will be used to encrypt the message in reverse order.
- tag: If provided, a 159-bit tag. If not provided, a new one
+ tag: If provided, a 159-bit tag. If not provided, a new one
is generated.
"""
if secretPRNG is None:
@@ -115,7 +115,7 @@
if tag is None:
tag = _getRandomTag(secretPRNG)
- header = _buildHeader(path, headerSecrets, exitType, tag+exitInfo,
+ header = _buildHeader(path, headerSecrets, exitType, tag+exitInfo,
paddingPRNG=Crypto.AESCounterPRNG())
return ReplyBlock(header, expiryTime,
@@ -123,7 +123,7 @@
path[0].getRoutingInfo().pack(), sharedKey), secrets, tag
# Maybe we shouldn't even allow this to be called with userKey==None.
-def buildStatelessReplyBlock(path, exitType, exitInfo, userKey,
+def buildStatelessReplyBlock(path, exitType, exitInfo, userKey,
expiryTime=0, secretRNG=None):
"""XXXX DOC IS NOW WRONG HERE
Construct a 'stateless' reply block that does not require the
@@ -146,7 +146,7 @@
"""
#XXXX Out of sync with the spec.
if secretRNG is None: secretRNG = Crypto.AESCounterPRNG()
-
+
while 1:
seed = _getRandomTag(secretRNG)
if Crypto.sha1(seed+userKey+"Validate")[-1] == '\x00':
@@ -164,17 +164,20 @@
Contract: return payload on success; raise MixError on certain failure,
return None if neither.
"""
+ if len(payload) != PAYLOAD_LEN or len(tag) != TAG_LEN:
+ raise MixError("Wrong payload or tag length")
+
if _checkPayload(payload):
return decodeForwardPayload(payload)
- if storedKeysFn is not None:
+ if storedKeys is not None:
secrets = storedKeys.get(tag)
if secrets is not None:
del storedKeys[tag]
return decodeReplyPayload(payload, secrets)
if userKey is not None:
- if Crypto.sha1(tag+userKey+"Validate")[-1] == '\x00':
+ if Crypto.sha1(tag+userKey+"Validate")[-1] == '\x00':
try:
return decodeStatelessReplyPayload(payload, tag, userKey)
except MixError, _:
@@ -189,10 +192,12 @@
def decodeForwardPayload(payload):
"XXXX"
- return _decodePayload(payload)
+ return decodePayloadImpl(payload)
def decodeEncryptedForwardPayload(payload, tag, key):
"XXXX"
+ assert len(tag) == TAG_LEN
+ assert len(payload) == PAYLOAD_LEN
msg = tag+payload
try:
rsaPart = Crypto.pk_decrypt(msg[:key.get_modulus_bytes()], key)
@@ -202,7 +207,7 @@
#XXXX magic string
k = Crypto.Keyset(rsaPart[:SECRET_LEN]).getLionessKeys("End-to-end encrypt")
rest = rsaPart[SECRET_LEN:] + Crypto.lioness_decrypt(rest, k)
- return _decodePayload(rest)
+ return decodePayloadImpl(rest)
def decodeReplyPayload(payload, secrets, check=0):
"XXXX"
@@ -213,14 +218,14 @@
if check and _checkPayload(payload):
break
- return _decodePayload(payload)
+ return decodePayloadImpl(payload)
def decodeStatelessReplyPayload(payload, tag, userKey):
"XXXX"
seed = Crypto.sha1(tag+userKey+"Generate")[:16]
prng = Crypto.AESCounterPRNG(seed)
secrets = [ prng.getBytes(SECRET_LEN) for _ in xrange(17) ]
-
+
return decodeReplyPayload(payload, secrets, check=1)
#----------------------------------------------------------------------
@@ -244,12 +249,12 @@
The following fields are optional:
paddingPRNG: A pseudo-random number generator used to pad the headers.
- If not provided, we use a counter-mode AES stream seeded from our
- entropy source.
-
+ If not provided, we use a counter-mode AES stream seeded from our
+ entropy source.
+
paranoia: If this is false, we use the padding PRNG to generate
header secrets too. Otherwise, we read all of our header secrets
- from the true entropy source.
+ from the true entropy source.
"""
assert len(payload) == PAYLOAD_LEN
reply = None
@@ -279,7 +284,7 @@
# Generate secrets for path1.
secrets1 = [ secretRNG.getBytes(SECRET_LEN) for _ in path1 ]
-
+
if path2:
# Make secrets for header 2, and construct header 2. We do this before
# making header1 so that our rng won't be used for padding yet.
@@ -308,14 +313,14 @@
if len(path) * ENC_SUBHEADER_LEN > HEADER_LEN:
raise MixError("Too many nodes in path")
- # Construct a list 'routing' of exitType, exitInfo.
+ # Construct a list 'routing' of exitType, exitInfo.
routing = [ (Modules.FWD_TYPE, node.getRoutingInfo().pack()) for
node in path[1:] ]
routing.append((exitType, exitInfo))
-
+
# sizes[i] is size, in blocks, of subheaders for i.
sizes =[ getTotalBlocksForRoutingInfoLen(len(ri)) for _, ri in routing]
-
+
# totalSize is the total number of blocks.
totalSize = reduce(operator.add, sizes)
if totalSize * ENC_SUBHEADER_LEN > HEADER_LEN:
@@ -324,8 +329,8 @@
# headerKey[i]==the AES key object node i will use to decrypt the header
headerKeys = [ Crypto.Keyset(secret).get(Crypto.HEADER_SECRET_MODE)
for secret in secrets ]
-
- # Calculate junk.
+
+ # Calculate junk.
# junkSeen[i]==the junk that node i will see, before it does any
# encryption. Note that junkSeen[0]=="", because node 0
# sees no junk.
@@ -383,7 +388,7 @@
"""
assert len(payload) == PAYLOAD_LEN
assert len(header1) == len(header2) == HEADER_LEN
-
+
if secrets2:
# (Copy secrets2 so we don't reverse the original)
secrets2 = secrets2[:]
@@ -409,7 +414,7 @@
secrets1 = secrets1[:]
# Now, encrypt header2 and the payload for each node in path1, reversed.
- secrets1.reverse()
+ secrets1.reverse()
for secret in secrets1:
ks = Crypto.Keyset(secret)
hkey = ks.getLionessKeys(Crypto.HEADER_ENCRYPT_MODE)
@@ -438,7 +443,7 @@
paddingLen = PAYLOAD_LEN - SINGLETON_PAYLOAD_OVERHEAD - overhead - length
if paddingLen < 0:
raise MixError("Payload too long for singleton message")
-
+
payload += paddingPRNG.getBytes(paddingLen)
return SingletonPayload(length, Crypto.sha1(payload), payload).pack()
@@ -448,7 +453,7 @@
b = ord(rng.getBytes(1)) & 0x7f
return chr(b) + rng.getBytes(TAG_LEN-1)
-def _decodePayload(payload):
+def decodePayloadImpl(payload):
if not _checkPayload(payload):
raise MixError("Hash doesn't match")
payload = parsePayload(payload)
@@ -468,29 +473,52 @@
_ZLIB_LIBRARY_OK = 0
def compressData(payload):
- """Given a string 'payload', compress it with zlib as specified in the
- remailer spec."""
+ """Given a string 'payload', compress it with the 'deflate' method
+ as specified in the remailer spec and in RFC1951."""
if not _ZLIB_LIBRARY_OK:
_validateZlib()
- # Don't change any of these options; they're all mandated.
+ # Don't change any of these options; they're all mandated.
zobj = zlib.compressobj(zlib.Z_BEST_COMPRESSION, zlib.DEFLATED,
- zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL,
+ zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL,
zlib.Z_DEFAULT_STRATEGY)
s1 = zobj.compress(payload)
s2 = zobj.flush()
- return s1 + s2
+ s = s1 + s2
+
+ # Now we check the 2 bytes of zlib header. Strictly speaking,
+ # these are irrelevant, as are the 4 bytes of adler-32 checksum at
+ # the end. Still, we can afford 6 bytes per payload, and
+ # reconstructing the checksum to keep zlib happy is a bit of a pain.
+ # XXXX doc manditory '\x78\xDA' beginning in spec.
+ assert s[0] == '\x78' # deflate, 32K window
+ assert s[1] == '\xda' # no dict, max compression
+ return s
def uncompressData(payload):
"""Uncompress a string 'payload'; raise ParseError if it is not valid
compressed data."""
+ # XXXX ???? How to prevent zlib bombing? Somebody could compress 28MB of
+ # XXXX ???? zero bytes down to fit in a single payload and use us to
+ # XXXX ???? mailbomb people, hard.
+ if len(payload) < 6 or payload[0:2] != '\x78\xDA':
+ raise ParseError("Invalid zlib header")
try:
- return zlib.decompress(payload)
+ # We can't just call zlib.decompress(payload), since we'll eventually
+ # want to limit the output size.
+ zobj = zlib.decompressobj(zlib.MAX_WBITS)
+ # Decompress the payload.
+ d = zobj.decompress(payload)
+ # Get any leftovers, which shouldn't exist.
+ nil = zobj.flush()
+ if nil != '':
+ raise ParseError("Error in compressed data")
+ return d
except zlib.error, _:
raise ParseError("Error in compressed data")
def _validateZlib():
- """Internal function: Make sure that zlib is a recognized version, and
- that it compresses things as expected. (This check is important,
+ """Internal function: Make sure that zlib is a recognized version, and
+ that it compresses things as expected. (This check is important,
because using a zlib version that compressed differently from zlib1.1.4
would make senders partitionable by payload compression.)
"""
@@ -498,6 +526,8 @@
ver = getattr(zlib, "ZLIB_VERSION")
if ver and ver < "1.1.2":
raise MixFatalError("Zlib version %s is not supported"%ver)
+
+ _ZLIB_LIBRARY_OK = 0.5
if ver in ("1.1.2", "1.1.3", "1.1.4"):
_ZLIB_LIBRARY_OK = 1
return
@@ -506,10 +536,10 @@
# This test is inadequate, but it _might_ catch future incompatible
# changes.
_ZLIB_LIBRARY_OK = 0.5
- good = 'x\xda\xed\xc6A\x11\x00 \x08\x00\xb0l\xd4\xf0\x87\x02\xf6o'+\
+ good = '\x78\xda\xed\xc6A\x11\x00 \x08\x00\xb0l\xd4\xf0\x87\x02\xf6o'+\
'`\x0e\xef\xb6\xd7r\xed\x88S=7\xcd\xcc\xcc\xcc\xcc\xcc\xcc'+\
- '\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xbe\xdd\x03q'+\
- '\x8d\n\x93'
+ '\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xbe\xdd\x03'+\
+ 'q\x8d\n\x93'
if compressData("aZbAAcdefg"*1000) == good:
_ZLIB_LIBRARY_OK = 1
else:
Index: Main.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Main.py,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- Main.py 31 Aug 2002 04:12:36 -0000 1.4
+++ Main.py 16 Oct 2002 23:12:12 -0000 1.5
@@ -64,6 +64,7 @@
try:
import mixminion.Main as _
except ImportError, _:
+ print >>sys.stderr, _
print >>sys.stderr,"Unable to find correct path for mixminion."
sys.exit(1)
Index: Modules.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Modules.py,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -d -r1.12 -r1.13
--- Modules.py 10 Sep 2002 14:45:30 -0000 1.12
+++ Modules.py 16 Oct 2002 23:12:12 -0000 1.13
@@ -19,6 +19,7 @@
import mixminion.Config
import mixminion.Packet
import mixminion.Queue
+import mixminion.BuildMessage
from mixminion.Config import ConfigError, _parseBoolean, _parseCommand
from mixminion.Common import getLog, createPrivateDir, MixError
@@ -99,6 +100,7 @@
DELIVER_FAIL_NORETRY (if the message shouldn't be tried later)."""
raise NotImplementedError("processMessage")
+
class ImmediateDeliveryQueue:
"""Helper class usable as delivery queue for modules that don't
actually want a queue. Such modules should have very speedy
@@ -270,7 +272,6 @@
getLog().error("Unable to handle message with unknown type %s",
exitType)
return
-
queue = self.queues[mod.getName()]
queue.queueMessage((exitType,exitInfo), message)
@@ -384,7 +385,7 @@
except KeyError, _:
getLog.warn("Unknown MBOX user %r", info.user)
- msg = _escapeMessageForEmail(message)
+ msg = _escapeMessageForEmail(message, info.tag)
fields = { 'user': address,
'return': self.returnAddress,
@@ -424,9 +425,19 @@
#----------------------------------------------------------------------
+#XXXX DOCDOC
_allChars = "".join(map(chr, range(256)))
_nonprinting = "".join(map(chr, range(0x00, 0x07)+range(0x0E, 0x20)))
-def _escapeMessageForEmail(msg):
+def _escapeMessageForEmail(msg, tag):
+ m = decodePayload(tag, msg)
+ if m is None:
+ junk = 1
+ msg = base64.encodestring(msg)
+ tag = base64.encodestring(tag)
+ else:
+ printable = m.translate(_allChars, _nonprinting)
+ allChars
+
printable = msg.translate(_allChars, _nonprinting)
if msg[len(printable):] == '\x00'*(len(msg)-len(printable)):
msg = msg[len(printable)]
@@ -445,3 +456,33 @@
%s
============ BASE-64 ENCODED ANONYMOUS MESSAGE ENDS\n""" % msg
+def _decodeMessage(self, message, exitInfo, text=0):
+ """XXXX DOCDOC
+ -> ("TXT"|"BIN"|"ENC", payload, RI, tag|None) or None
+ """
+ if len(exitInfo) < 20:
+ return None
+ tag = exitInfo[:20]
+ ri = exitInfo[20:]
+
+ try:
+ m = mixminion.BuildMessage.decodePayload(message, tag)
+ except MixError, e:
+ return None
+
+ if m is None:
+ code = "ENC"
+ else:
+ tag = None
+ printable = msg.translate(_allChars, _nonprinting)
+ if msg[len(printable):] == '\x00'*(len(msg)-len(printable)):
+ code = "TXT"
+ else:
+ code = "BIN"
+
+ if text and (code != "TXT") :
+ message = base64.encodestring(message)
+ if text and tag:
+ tag = base64.encodestring(tag)
+
+ return code, message, ri, tag
Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -d -r1.12 -r1.13
--- Packet.py 14 Oct 2002 03:03:42 -0000 1.12
+++ Packet.py 16 Oct 2002 23:12:12 -0000 1.13
@@ -30,15 +30,18 @@
# Length of a single payload
PAYLOAD_LEN = MESSAGE_LEN - HEADER_LEN*2
+# Bytes taken up by OAEP padding in RSA-encrypted data
+OAEP_OVERHEAD = 42
+
+# Length of a subheader, once RSA-encoded.
+ENC_SUBHEADER_LEN = 128
# Smallest possible size for a subheader
MIN_SUBHEADER_LEN = 42
-# Most information we can fit into a subheader
-MAX_SUBHEADER_LEN = 86
+# Most information we can fit into a subheader before padding
+MAX_SUBHEADER_LEN = ENC_SUBHEADER_LEN - OAEP_OVERHEAD
# Longest routing info that will fit in the main subheader
MAX_ROUTING_INFO_LEN = MAX_SUBHEADER_LEN - MIN_SUBHEADER_LEN
-# Length of a subheader, once RSA-encoded.
-ENC_SUBHEADER_LEN = 128
# Length of a digest
DIGEST_LEN = 20
# Length of a secret key
@@ -107,7 +110,7 @@
Returns a slice of the i-j'th subheaders of this header."""
if j > 16: j = 16
if i < 0: i += 16
- if j < 0: j += 16
+ if j < 0: j += 16
return self.contents[i*ENC_SUBHEADER_LEN:
j*ENC_SUBHEADER_LEN]
@@ -237,19 +240,25 @@
#----------------------------------------------------------------------
# UNENCRYPTED PAYLOADS
-# XXXX DOCUMENT CONTENTS
+# Length of the 'MessageID' field in a fragment payload
FRAGMENT_MESSAGEID_LEN = 20
+# Maximum number of fragments associated with a given message
MAX_N_FRAGMENTS = 0x7ffff
-#XXXX DOCDOC
+# Number of bytes taken up by header fields in a singleton payload.
SINGLETON_PAYLOAD_OVERHEAD = 2 + DIGEST_LEN
+# Number of bytes taken up by header fields in a fragment payload.
FRAGMENT_PAYLOAD_OVERHEAD = 2 + DIGEST_LEN + FRAGMENT_MESSAGEID_LEN + 4
-OAEP_OVERHEAD = 42
-#XXXX DOC DOC and e2e note is off by 4.
+# Number of bytes taken up from OAEP padding in an encrypted forward
+# payload, minus bytes saved by spilling the RSA-encrypted block into the
+# tag, minus the bytes taken by the session key.
+# XXXX (The e2e note is off by 4.)
ENC_FWD_OVERHEAD = OAEP_OVERHEAD - TAG_LEN + SECRET_LEN
def parsePayload(payload):
- "XXXX"
+ """Convert a decoded mixminion payload into a SingletonPayload or a
+ FragmentPayload object. Raise ParseError on failure or data
+ corruption."""
if len(payload) not in (PAYLOAD_LEN, PAYLOAD_LEN-ENC_FWD_OVERHEAD):
raise ParseError("Payload has bad length")
bit0 = ord(payload[0]) & 0x80
@@ -258,13 +267,13 @@
idx, hash, msgID, msgLen = struct.unpack(FRAGMENT_UNPACK_PATTERN,
payload[:FRAGMENT_PAYLOAD_OVERHEAD])
idx &= 0x7f
- contents = payload[FRAGMENT_PAYLOAD_OVERHEAD:]
+ contents = payload[FRAGMENT_PAYLOAD_OVERHEAD:]
if msgLen <= len(contents):
raise ParseError("Payload has an invalid size field")
return FragmentPayload(idx,hash,msgID,msgLen,contents)
else:
# We have a singleton
- size, hash = struct.unpack(SINGLETON_UNPACK_PATTERN,
+ size, hash = struct.unpack(SINGLETON_UNPACK_PATTERN,
payload[:SINGLETON_PAYLOAD_OVERHEAD])
contents = payload[SINGLETON_PAYLOAD_OVERHEAD:]
if size > len(contents):
@@ -279,22 +288,24 @@
FRAGMENT_UNPACK_PATTERN = "!H%ds%dsL" % (DIGEST_LEN, FRAGMENT_MESSAGEID_LEN)
class SingletonPayload:
- "XXXX"
+ """Represents the payload for a standalone mixminion message.
+ Fields: size, hash, data. (Note that data is padded.)"""
def __init__(self, size, hash, data):
self.size = size
self.hash = hash
self.data = data
def isSingleton(self):
- "XXXX"
+ """Returns true; this is a singleton payload."""
return 1
def getContents(self):
- "XXXX"
+ """Returns the non-padding portion of this payload's data"""
return self.data[:self.size]
def pack(self):
- "XXXX"
+ """Check for reasonable values of fields, and return a packed payload.
+ """
assert (0x8000 & self.size) == 0
assert 0 <= self.size <= len(self.data)
assert len(self.hash) == DIGEST_LEN
@@ -304,7 +315,7 @@
return "%s%s" % (header, self.data)
class FragmentPayload:
- "XXXX"
+ """Represents the fields of a decoded fragment payload."""
def __init__(self, index, hash, msgID, msgLen, data):
self.index = index
self.hash = hash
@@ -313,11 +324,11 @@
self.data = data
def isSingleton(self):
- "XXXX"
+ """Return false; not a singleton"""
return 0
def pack(self):
- "XXXX"
+ """Returns the string value of this payload."""
assert 0 <= self.index <= MAX_N_FRAGMENTS
assert len(self.hash) == DIGEST_LEN
assert len(self.msgID) == FRAGMENT_MESSAGEID_LEN
@@ -374,10 +385,10 @@
def pack(self):
"""Returns the external representation of this reply block"""
return struct.pack(RB_UNPACK_PATTERN,
- "SURB", 0x00, 0x01, self.timestamp, self.header,
- len(self.routingInfo), self.routingType,
+ "SURB", 0x00, 0x01, self.timestamp, self.header,
+ len(self.routingInfo), self.routingType,
self.encryptionKey) + self.routingInfo
-
+
#----------------------------------------------------------------------
# Routing info
@@ -413,9 +424,9 @@
def pack(self):
"""Return the routing info for this address"""
assert len(self.keyinfo) == DIGEST_LEN
- return struct.pack(IPV4_PAT, inet_aton(self.ip),
+ return struct.pack(IPV4_PAT, inet_aton(self.ip),
self.port, self.keyinfo)
-
+
def __hash__(self):
return hash(self.pack())
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.30
retrieving revision 1.31
diff -u -d -r1.30 -r1.31
--- test.py 14 Oct 2002 03:03:42 -0000 1.30
+++ test.py 16 Oct 2002 23:12:12 -0000 1.31
@@ -26,12 +26,17 @@
from mixminion.testSupport import mix_mktemp
from mixminion.Common import MixError, MixFatalError, MixProtocolError, getLog
+import mixminion.Crypto as Crypto
try:
import unittest
except ImportError:
import mixminion._unittest as unittest
+# Set this flag to 1 in order to have all RSA keys and diffie-hellman params
+# generated independently.
+USE_SLOW_MODE = 0
+
def hexread(s):
assert (len(s) % 2) == 0
r = []
@@ -71,6 +76,39 @@
del log._storedHandlers
return str(buf)
+# RSA key caching functionality
+_generated_rsa_keys = {}
+def getRSAKey(n,bits):
+ """Return the n'th of an arbitrary number of cached 'bits'-bit RSA keys,
+ generating them as necessary."""
+ try:
+ return _generated_rsa_keys[(n,bits)]
+ except KeyError, _:
+ if bits > 1024:
+ print "[generating %d-bit key #%d..."%(bits,n),
+ sys.stdout.flush()
+ k = _pk_generate_orig(bits)
+ if bits > 1024:
+ print "done]",
+ sys.stdout.flush()
+ _generated_rsa_keys[(n,bits)] = k
+ return k
+
+# Functions to override Crypto.pk_generate to avoid generating a zillion RSA
+# keys.
+_pk_generate_orig = Crypto.pk_generate
+_pk_generate_idx = 0
+def _pk_generate_replacement(bits=1024,e=65537):
+ if bits == 1024:
+ global _pk_generate_idx
+ _pk_generate_idx = (_pk_generate_idx + 1) % 4
+ return getRSAKey(_pk_generate_idx, bits)
+ else:
+ return getRSAKey(0, bits)
+
+if not USE_SLOW_MODE:
+ Crypto.pk_generate = _pk_generate_replacement
+
#----------------------------------------------------------------------
# Tests for common functionality
@@ -239,7 +277,7 @@
check,x[:-1]+ch,"B",128)
def test_rsa(self):
- p = _ml.rsa_generate(1024, 65537)
+ p = getRSAKey(1,1024)
#for all of SIGN, CHECK_SIG, ENCRYPT, DECRYPT...
for pub1 in (0,1):
@@ -303,17 +341,18 @@
p.PEM_write_key(open(tf_enc,'w'), 0, "top sekrit")
p2 = _ml.rsa_PEM_read_key(open(tf_pub, 'r'), 1)
self.assertEquals(p.get_public_key(), p2.get_public_key())
-
+
p2 = _ml.rsa_PEM_read_key(open(tf_prv, 'r'), 0)
self.assertEquals(p.encode_key(0), p2.encode_key(0))
self.failUnlessRaises(_ml.CryptoError,
_ml.rsa_PEM_read_key,
open(tf_enc, 'r'), 0)
-
+
p2 = _ml.rsa_PEM_read_key(open(tf_prv, 'r'), 0, "top sekrit")
self.assertEquals(p.encode_key(0), p2.encode_key(0))
-
+
+
#----------------------------------------------------------------------
import mixminion.Crypto
from mixminion.Crypto import *
@@ -339,8 +378,8 @@
def test_rsa(self):
eq = self.assertEquals
- k512 = pk_generate(512)
- k1024 = pk_generate()
+ k512 = getRSAKey(0,512)
+ k1024 = getRSAKey(0,1024)
eq(512>>3, k512.get_modulus_bytes())
eq(1024>>3, k1024.get_modulus_bytes())
@@ -386,7 +425,7 @@
# Test pickling
init_crypto()
s = cPickle.dumps(k512)
- self.assertEquals(cPickle.loads(s).get_public_key(),
+ self.assertEquals(cPickle.loads(s).get_public_key(),
k512.get_public_key())
def test_trng(self):
@@ -500,6 +539,7 @@
s.sort()
self.assertEquals(s, range(100))
+
#----------------------------------------------------------------------
import mixminion.Packet
from mixminion.Packet import *
@@ -612,7 +652,7 @@
self.failUnlessRaises(ParseError, parseIPV4Info, ri[:-1])
self.failUnlessRaises(ParseError, parseIPV4Info, ri+"x")
-
+
def test_smtpinfomboxinfo(self):
tag = "\x01\x02"+"Z"*18
@@ -719,6 +759,7 @@
self.failUnlessRaises(ParseError,parsePayload,bad_payload_1)
self.failUnlessRaises(ParseError,parsePayload,bad_payload_2)
+
#----------------------------------------------------------------------
from mixminion.HashLog import HashLog
@@ -781,6 +822,7 @@
h[0].close()
+
#----------------------------------------------------------------------
import mixminion.BuildMessage as BuildMessage
from mixminion.Modules import *
@@ -789,11 +831,6 @@
def getBytes(self,n):
return "\x00"*n
-class BMTSupport:
- pk1 = pk_generate()
- pk2 = pk_generate()
- pk3 = pk_generate()
-
class FakeServerInfo:
"""Represents a Mixminion server, and the information needed to send
messages to it."""
@@ -807,7 +844,7 @@
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."""
@@ -815,9 +852,10 @@
class BuildMessageTests(unittest.TestCase):
def setUp(self):
- self.pk1 = BMTSupport.pk1
- self.pk2 = BMTSupport.pk2
- self.pk3 = BMTSupport.pk3
+ self.pk1 = getRSAKey(0,1024)
+ self.pk2 = getRSAKey(1,1024)
+ self.pk3 = getRSAKey(2,1024)
+ self.pk512 = getRSAKey(0,512)
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)
@@ -825,7 +863,7 @@
def test_compression(self):
p = AESCounterPRNG()
longMsg = p.getBytes(100)*2 + str(dir(mixminion.Crypto))
-
+
# Make sure compression is reversible.
for m in ("", "a", "\000", "xyzzy"*10, ("glossy glossolalia.."*2)[32],
longMsg):
@@ -861,11 +899,21 @@
comp = BuildMessage.compressData(m)
self.assert_(pld[22:].startswith(comp))
self.assertEquals(sha1(pld[22:]),pld[2:22])
+ self.assert_(BuildMessage._checkPayload(pld))
self.assertEquals(len(comp), ord(pld[0])*256+ord(pld[1]))
self.assertEquals(0, ord(pld[0])&0x80)
- self.assertEquals(m, BuildMessage._decodePayload(pld))
+ self.assertEquals(m, BuildMessage.decodePayloadImpl(pld))
- # XXXX Test failing cases
+ self.failUnlessRaises(MixError, BuildMessage.decodePayloadImpl, b)
+
+ # Check fragments (not yet supported)
+ pldFrag = chr(ord(pld[0])|0x80)+pld[1:]
+ self.failUnlessRaises(MixError, BuildMessage.decodePayloadImpl,pldFrag)
+
+ # Check impossibly long messages
+ pldSize = "\x7f\xff"+pld[2:] #sha1(pld[22:])+pld[22:]
+ self.failUnlessRaises(ParseError,
+ BuildMessage.decodePayloadImpl,pldSize)
def test_buildheader_1hop(self):
bhead = BuildMessage._buildHeader
@@ -1090,13 +1138,15 @@
payload, decoder=None):
"""Decrypts the layers of a message one by one, checking them for
correctness.
- XXXX Document decoder
msg: the message to examine
header_info_1: a tuple of (pks,secrets,rtypes,rinfo)
as used by do_header_test for the first header.
header_info_2: a tuple of (pks,secrets,rtypes,rinfo)
as used by do_header_test for the second header.
payload: The beginning of the expected decrypted payload.
+ decoder: A function to call on the exit payload before
+ comparing it to 'payload'. Takes payload,tag;
+ returns string.
"""
# Check header 1, and get secrets
sec = self.do_header_test(msg[:2048], *header_info_1)
@@ -1125,7 +1175,7 @@
def test_build_fwd_message(self):
bfm = BuildMessage.buildForwardMessage
befm = BuildMessage.buildEncryptedForwardMessage
- payload = "Hello"
+ payload = "Hello!!!!"
m = bfm(payload, 500, "Goodbye",
[self.server1, self.server2],
@@ -1140,12 +1190,18 @@
(FWD_TYPE, 500),
(self.server2.getRoutingInfo().pack(),
"Goodbye") ),
- "Hello")
+ "Hello!!!!")
m = bfm(payload, 500, "Goodbye",
[self.server1,],
[self.server3,])
+ messages = {}
+
+ def decoder0(p,t,messages=messages):
+ messages['fwd'] = (p,t)
+ return BuildMessage.decodeForwardPayload(p)
+
self.do_message_test(m,
( (self.pk1,), None,
(SWAP_FWD_TYPE,),
@@ -1153,16 +1209,19 @@
( (self.pk3,), None,
(500,),
("Goodbye",) ),
- "Hello")
+ "Hello!!!!",
+ decoder=decoder0)
# Encrypted forward message
- rsa1, rsa2 = pk_generate(1024), pk_generate(2048)
+ rsa1, rsa2 = self.pk1, self.pk512
+ payload = "<<<<Hello>>>>" * 100
for rsakey in rsa1,rsa2:
- m = befm(payload, 500, "Phello",
+ m = befm(payload, 500, "Phello",
[self.server1, self.server2],
[self.server3, self.server2],
rsakey)
- def decoder(p,t,key=rsakey):
+ def decoder(p,t,key=rsakey,messages=messages):
+ messages['efwd'+str(key.get_modulus_bytes())] = (p,t)
return BuildMessage.decodeEncryptedForwardPayload(p,t,key)
self.do_message_test(m,
@@ -1177,18 +1236,42 @@
payload,
decoder=decoder)
+ # Now do more tests on final messages: is the format as expected?
+ p,t = messages['fwd']
+ self.assertEquals(20, len(t))
+ self.assertEquals(28*1024,len(p))
+ self.assertEquals(0, ord(t[0]) & 0x80)
+ comp = BuildMessage.compressData("Hello!!!!")
+ self.assertEquals(len(comp), ord(p[0])*256 +ord(p[1]))
+ self.assert_(p[22:].startswith(comp))
+ self.assertEquals(sha1(p[22:]), p[2:22])
+
+ for rsakey in (rsa1, rsa2):
+ n = rsakey.get_modulus_bytes()
+ p,t = messages['efwd'+str(n)]
+ mrsa, mrest = t+p[:n-20], p[n-20:]
+ mrsa = pk_decrypt(mrsa, rsakey)
+ sessionkey, rsa_rest = mrsa[:16], mrsa[16:]
+ ks = Keyset(sessionkey)
+ msg = rsa_rest + lioness_decrypt(mrest,
+ ks.getLionessKeys("End-to-end encrypt"))
+ comp = BuildMessage.compressData(payload)
+ self.assert_(len(comp), ord(msg[0])*256 + ord(msg[1]))
+ self.assertEquals(sha1(msg[22:]), msg[2:22])
+ self.assert_(msg[22:].startswith(comp))
+
def test_buildreply(self):
brb = BuildMessage.buildReplyBlock
bsrb = BuildMessage.buildStatelessReplyBlock
brm = BuildMessage.buildReplyMessage
## Stateful reply blocks.
- reply, secrets, tag = \
+ reply, secrets_1, tag_1 = \
brb([self.server3, self.server1, self.server2,
self.server1, self.server3],
SMTP_TYPE,
"no-such-user@invalid", tag=("+"*20))
- hsecrets = secrets[:-1]
+ hsecrets = secrets_1[:-1]
hsecrets.reverse()
pks_1 = (self.pk3, self.pk1, self.pk2, self.pk1, self.pk3)
@@ -1199,11 +1282,13 @@
self.assert_(reply.routingInfo == self.server3.getRoutingInfo().pack())
- m = brm("Information?",
+ m = brm("Information???",
[self.server3, self.server1],
reply)
- def decoder(p,t,secrets=secrets):
+ messages = {}
+ def decoder(p,t,secrets=secrets_1,messages=messages):
+ messages['repl'] = p,t
return BuildMessage.decodeReplyPayload(p,secrets)
self.do_message_test(m,
@@ -1214,12 +1299,12 @@
(pks_1, hsecrets,
(FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,SMTP_TYPE),
infos+("no-such-user@invalid",)),
- "Information?",
+ "Information???",
decoder=decoder)
## Stateless replies
reply = bsrb([self.server3, self.server1, self.server2,
self.server1, self.server3], MBOX_TYPE,
- "fred", "Galaxy Far Away", 0)
+ "fred", "Tyrone Slothrop", 0)
sec,(loc,) = self.do_header_test(reply.header, pks_1, None,
(FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,MBOX_TYPE),
@@ -1228,17 +1313,25 @@
self.assertEquals(loc[20:], "fred")
seed = loc[:20]
- prng = AESCounterPRNG(sha1(seed+"Galaxy Far AwayGenerate")[:16])
+ prng = AESCounterPRNG(sha1(seed+"Tyrone SlothropGenerate")[:16])
sec.reverse()
self.assertEquals(sec, [ prng.getBytes(16) for _ in range(len(sec)) ])
- m = brm("Information? What's the world come to?",
+ # _Gravity's Rainbow_, page 258.
+ payload = '''
+ "...Is it any wonder the world's gone insane, with information
+ come to the be the only medium of exchange?"
+ "I thought it was cigarettes."
+ "You dream."
+ -- Gravity's Rainbow, p.258 ''' # " <- for emacs python-mode
+ m = brm(payload,
[self.server3, self.server1],
reply)
- def decoder2(p,t):
+ def decoder2(p,t,messages=messages):
+ messages['srepl'] = p,t
return BuildMessage.decodeStatelessReplyPayload(p,t,
- "Galaxy Far Away")
+ "Tyrone Slothrop")
self.do_message_test(m,
((self.pk3, self.pk1), None,
(FWD_TYPE,SWAP_FWD_TYPE),
@@ -1247,8 +1340,101 @@
(pks_1, None,
(FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,MBOX_TYPE),
infos+("fred",)),
- "Information? What's the world come to?",
+ payload,
decoder=decoder2)
+
+ # Now test format of generated messages.
+ p,t = messages['repl']
+ self.assertEquals(t, tag_1)
+ for s in secrets_1:
+ ks = Keyset(s)
+ p = lioness_encrypt(p, ks.getLionessKeys(
+ mixminion.Crypto.PAYLOAD_ENCRYPT_MODE))
+ comp = BuildMessage.compressData('Information???')
+ self.assertEquals(len(comp), ord(p[0])*256 +ord(p[1]))
+ self.assert_(p[22:].startswith(comp))
+ self.assertEquals(sha1(p[22:]), p[2:22])
+
+ p,t = messages['srepl']
+ self.assertEquals('\000', sha1(t+"Tyrone SlothropValidate")[-1])
+ prng = AESCounterPRNG(sha1(t+"Tyrone SlothropGenerate")[:16])
+ for _ in xrange(6): # 5 hops plus end-to-end
+ s = prng.getBytes(16)
+ ks = Keyset(s)
+ p = lioness_encrypt(p, ks.getLionessKeys(
+ mixminion.Crypto.PAYLOAD_ENCRYPT_MODE))
+ comp = BuildMessage.compressData(payload)
+ self.assertEquals(len(comp), ord(p[0])*256 +ord(p[1]))
+ self.assert_(p[22:].startswith(comp))
+ self.assertEquals(sha1(p[22:]), p[2:22])
+
+ def test_decoding(self):
+ # Now we create a bunch of fake payloads and try to decode them.
+
+ # Successful messages:
+ payload = "All dreamers and sleepwalkers must pay the price, and "+\
+ "even the invisible victim is responsible for the fate of all.\n"+\
+ " -- _Invisible Man_"
+
+ comp = BuildMessage.compressData(payload)
+ self.assertEquals(len(comp), 109)
+ encoded1 = (comp+ "RWE/HGW"*4096)[:28*1024-22]
+ encoded1 = '\x00\x6D'+sha1(encoded1)+encoded1
+ # Forward message.
+ self.assertEquals(payload, BuildMessage.decodeForwardPayload(encoded1))
+
+ # Encoded forward message
+ efwd = (comp+"RWE/HGW"*4096)[:28*1024-22-38]
+ efwd = '\x00\x6D'+sha1(efwd)+efwd
+ rsa1 = self.pk1
+ key1 = Keyset("RWE "*4).getLionessKeys("End-to-end encrypt")
+ efwd_rsa = pk_encrypt(("RWE "*4)+efwd[:70], rsa1)
+ efwd_lioness = lioness_encrypt(efwd[70:], key1)
+ efwd_t = efwd_rsa[:20]
+ efwd_p = efwd_rsa[20:]+efwd_lioness
+ self.assertEquals(payload,
+ BuildMessage.decodeEncryptedForwardPayload(efwd_p,efwd_t,rsa1))
+
+ # Stateful reply
+ secrets = [ "Is that you, Des","troyer?Rinehart?" ]
+ sdict = { 'tag1'*5 : secrets }
+ ks = Keyset(secrets[1])
+ m = lioness_decrypt(encoded1, ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE))
+ ks = Keyset(secrets[0])
+ m = lioness_decrypt(m, ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE))
+ self.assertEquals(payload, BuildMessage.decodeReplyPayload(m,secrets))
+ repl1 = m
+
+ # Stateless reply
+ tag = "To light my way out\xBE"
+ passwd = "out I would have to burn every paper in the briefcase"
+ self.assertEquals('\000', sha1(tag+passwd+"Validate")[-1])
+ prng = AESCounterPRNG(sha1(tag+passwd+"Generate")[:16])
+ secrets2 = [ prng.getBytes(16) for _ in xrange(5) ]
+ m = encoded1
+ s = secrets2[:]
+ s.reverse()
+ for k in s:
+ key = Keyset(k).getLionessKeys(PAYLOAD_ENCRYPT_MODE)
+ m = lioness_decrypt(m,key)
+ self.assertEquals(payload, BuildMessage.decodeStatelessReplyPayload(m,tag,passwd))
+ repl2 = m
+
+ # Okay, now let's try out 'decodePayload'.
+ decodePayload = BuildMessage.decodePayload
+ self.assertEquals(payload,
+ decodePayload(encoded1, "zzzz"*5, self.pk1, sdict, passwd))
+ self.assertEquals(payload,
+ decodePayload(efwd_p, efwd_t, self.pk1, sdict, passwd))
+ self.assert_(sdict)
+ self.assertEquals(payload,
+ decodePayload(repl1, "tag1"*5, self.pk1, sdict, passwd))
+ self.assert_(not sdict)
+ self.assertEquals(payload,
+ decodePayload(repl2, tag, self.pk1, sdict, passwd))
+
+ # And now the failing cases
+ # XXXX TEST THESE
#----------------------------------------------------------------------
@@ -1260,9 +1446,9 @@
class PacketHandlerTests(unittest.TestCase):
def setUp(self):
from mixminion.PacketHandler import PacketHandler
- self.pk1 = BMTSupport.pk1
- self.pk2 = BMTSupport.pk2
- self.pk3 = BMTSupport.pk3
+ self.pk1 = getRSAKey(0,1024)
+ self.pk2 = getRSAKey(1,1024)
+ self.pk3 = getRSAKey(2,1024)
self.tmpfile = mix_mktemp(".db")
h = self.hlog = HashLog(self.tmpfile, "Z"*20)
@@ -1278,11 +1464,10 @@
self.hlog.close()
def do_test_chain(self, m, sps, routingtypes, routinginfo, payload,
- appkey=None, decoder=None):
+ appkey=None):
"""Routes a message through a series of servers, making sure that
each one decrypts it properly and routes it correctly to the
next.
- XXXX DOC DECODER
m: the message to test
sps: sequence of PacketHandler objects for m's path
routingtypes: sequence of expected routingtype
@@ -1304,10 +1489,7 @@
self.assertEquals(appkey, res[1][2])
p = res[1][3]
- if decoder is None:
- p = BuildMessage.decodeForwardPayload(p)
- else:
- p = decoder(p, res[1][1][:20])
+ p = BuildMessage.decodeForwardPayload(p)
self.assert_(p.startswith(payload))
break
@@ -1475,6 +1657,7 @@
q, (a, m_x) = self.sp2.processMessage(m_x)
self.failUnlessRaises(CryptoError, self.sp3.processMessage, m_x)
+
#----------------------------------------------------------------------
# QUEUE
@@ -1494,7 +1677,7 @@
self.d1 = mix_mktemp("q1")
self.d2 = mix_mktemp("q2")
self.d3 = mix_mktemp("q3")
-
+
def testCreateQueue(self):
# Nonexistent dir.
self.failUnlessRaises(MixFatalError, Queue, self.d1)
@@ -1533,7 +1716,7 @@
queue2 = Queue(self.d3, create=1)
# Put 100 messages in queue1
- handles = [queue1.queueMessage("Sample message %s" % i)
+ handles = [queue1.queueMessage("Sample message %s" % i)
for i in range(100)]
hdict = {}
for i in range(100): hdict[handles[i]] = i
@@ -1578,7 +1761,7 @@
for h in handles[30:60]:
queue1.removeMessage(h)
self.assertEquals(40, queue1.count())
-
+
# Make sure that smaller pickRandoms work.
L1 = queue1.pickRandom(10)
L2 = queue1.pickRandom(10)
@@ -1592,7 +1775,7 @@
f.close()
self.assertEquals(s, "Sample message 60")
- # test successful 'openNewMessage'
+ # test successful 'openNewMessage'
f, h = queue1.openNewMessage()
f.write("z"*100)
self.failUnlessRaises(IOError, queue1.messageContents, h)
@@ -1620,9 +1803,9 @@
# Scrub both queues.
queue1.removeAll()
queue2.removeAll()
- queue1.cleanQueue()
+ queue1.cleanQueue()
queue2.cleanQueue()
-
+
def testDeliveryQueues(self):
d_d = mix_mktemp("qd")
@@ -1708,6 +1891,7 @@
bcmq.removeAll()
bcmq.cleanQueue()
+
#---------------------------------------------------------------------
# LOGGING
class LogTests(unittest.TestCase):
@@ -1728,7 +1912,7 @@
log.error("All your anonymity are belong to us")
self.failUnless(buf.getvalue().endswith(
"[ERROR] All your anonymity are belong to us\n"))
-
+
buf.truncate(0)
try:
raise MixError()
@@ -1756,6 +1940,7 @@
self.assertEquals(open(t).read().count("\n") , 1)
self.assertEquals(open(t1).read().count("\n"), 3)
+
#----------------------------------------------------------------------
# File paranoia
from mixminion.Common import createPrivateDir, checkPrivateDir
@@ -1810,7 +1995,7 @@
self.failUnlessRaises(MixFatalError, checkPrivateDir, subdir)
os.chown(noia, 0, os.getgid())
else:
- # We're not root. We can't reliably find or make a directory
+ # We're not root. We can't reliably find or make a directory
# that's non-root and non-us. Let's just make sure we don't
# own temp.
if os.path.exists("/tmp"):
@@ -1866,6 +2051,7 @@
# SIGHANDLERS
# FFFF Write tests here
+
#----------------------------------------------------------------------
# MMTP
# FFFF Write more tests
@@ -1888,23 +2074,25 @@
pkfile = f+"_pk"
certfile = f+"_cert"
dh_fname = os.environ.get("MM_TEST_DHPARAMS", None)
- if dh_fname:
+ if dh_fname and not USE_SLOW_MODE:
dhfile = dh_fname
if not os.path.exists(dh_fname):
print "[Generating DH parameters...",
sys.stdout.flush()
_ml.generate_dh_parameters(dhfile, 0)
- print "done.]"
+ print "done.]",
+ sys.stdout.flush()
else:
print "[Generating DH parameters (not caching)...",
sys.stdout.flush()
_ml.generate_dh_parameters(dhfile, 0)
- print "done.]"
- pk = _ml.rsa_generate(1024, 65537)
+ print "done.]",
+ sys.stdout.flush()
+ pk = getRSAKey(3,1024)
pk.PEM_write_key(open(pkfile, 'w'), 0)
_ml.generate_cert(certfile, pk, "Testing certificate",
time.time(), time.time()+365*24*60*60)
-
+
pk = _ml.rsa_PEM_read_key(open(pkfile, 'r'), 0)
return _ml.TLSContext_new(certfile, pk, dhfile)
else:
@@ -1926,7 +2114,7 @@
listener.register(server)
pk = _ml.rsa_PEM_read_key(open(pkfile, 'r'), public=0)
keyid = sha1(pk.encode_key(1))
-
+
return server, listener, messagesIn, keyid
class MMTPTests(unittest.TestCase):
@@ -1950,12 +2138,12 @@
def testNonblockingTransmission(self):
self.doTest(self._testNonblockingTransmission)
-
+
def _testBlockingTransmission(self):
server, listener, messagesIn, keyid = _getMMTPServer()
self.listener = listener
self.server = server
-
+
messages = ["helloxxx"*4096, "helloyyy"*4096]
server.process(0.1)
@@ -1982,7 +2170,7 @@
while t.isAlive():
server.process(0.1)
t.join()
-
+
def _testNonblockingTransmission(self):
server, listener, messagesIn, keyid = _getMMTPServer()
@@ -1992,16 +2180,16 @@
messages = ["helloxxx"*4096, "helloyyy"*4096]
async = mixminion.MMTPServer.AsyncServer()
clientcon = mixminion.MMTPServer.MMTPClientConnection(
- _getTLSContext(0), "127.0.0.1", TEST_PORT, keyid, messages[:],
+ _getTLSContext(0), "127.0.0.1", TEST_PORT, keyid, messages[:],
[None, None], None)
clientcon.register(async)
def clientThread(clientcon=clientcon, async=async):
while not clientcon.isShutdown():
async.process(2)
-
+
server.process(0.1)
t = threading.Thread(None, clientThread)
-
+
t.start()
while len(messagesIn) < 2:
server.process(0.1)
@@ -2020,7 +2208,7 @@
def clientThread(clientcon=clientcon, async=async):
while not clientcon.isShutdown():
async.process(2)
-
+
severity = getLog().getMinSeverity()
try:
suspendLog() # suppress warning
@@ -2033,7 +2221,8 @@
t.join()
finally:
resumeLog() #unsuppress warning
-
+
+
#----------------------------------------------------------------------
# Config files
@@ -2077,13 +2266,13 @@
Bar bar
Baz:
baz
- and more baz
- and more baz
+ and more baz
+ and more baz
[Sec2]
# Comment
Bap +
-Quz 99 99
+Quz 99 99
Fob=1
@@ -2098,7 +2287,7 @@
IntRS=5
"""
-
+
f = TCF(string=longerString)
self.assertEquals(f['Sec1']['Foo'], 'abcde f')
self.assertEquals(f['Sec1']['Bar'], 'bar')
@@ -2119,7 +2308,7 @@
"IntAMD: 10\nIntRS: 5\n\n"))
# Test file input
fn = mix_mktemp()
-
+
file = open(fn, 'w')
file.write(longerString)
file.close()
@@ -2144,7 +2333,7 @@
self.assertEquals(f['Sec1']['Foo'], 'abcde f')
self.assertEquals(f['Sec1']['Bar'], 'bar')
self.assertEquals(f['Sec2']['Quz'], ['99 99', '88 88'])
-
+
# Test 'reload' operation
file = open(fn, 'w')
file.write(shorterString)
@@ -2227,7 +2416,7 @@
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)
@@ -2316,12 +2505,8 @@
Nickname: fred-the-bunny
"""
-_IDENTITY_KEY = None
def _getIdentityKey():
- global _IDENTITY_KEY
- if _IDENTITY_KEY is None:
- _IDENTITY_KEY = mixminion.Crypto.pk_generate(2048)
- return _IDENTITY_KEY
+ return getRSAKey(0,2048)
import mixminion.Config
import mixminion.ServerInfo
@@ -2355,7 +2540,7 @@
eq(info['Server']['Contact'], "a@b.c")
eq(info['Server']['Comments'],
"This is a test of the emergency broadcast system")
-
+
eq(info['Incoming/MMTP']['Version'], "0.1")
eq(info['Incoming/MMTP']['Port'], 48099)
eq(info['Incoming/MMTP']['Protocols'], "0.1")
@@ -2403,7 +2588,7 @@
d,
"key2",
d)
-
+
# Now with a bad signature
sig2 = mixminion.Crypto.pk_sign(sha1("Hello"), identity)
sig2 = base64.encodestring(sig2).replace("\n", "")
@@ -2421,7 +2606,8 @@
self.failUnlessRaises(ConfigError,
mixminion.ServerInfo.ServerInfo,
None, badSig)
-
+
+
#----------------------------------------------------------------------
# Modules annd ModuleManager
from mixminion.Modules import *
@@ -2438,7 +2624,7 @@
def getName(self):
return "TestModule"
def getConfigSyntax(self):
- return { "Example" : { "Foo" : ("REQUIRE",
+ return { "Example" : { "Foo" : ("REQUIRE",
mixminion.Config._parseInt, None) } }
def validateConfig(self, cfg, entries, lines, contents):
if cfg['Example'] is not None:
@@ -2519,10 +2705,10 @@
manager.queueMessage("Hello 1", 1234, "fail!")
manager.queueMessage("Hello 2", 1234, "fail?")
manager.queueMessage("Hello 3", 1234, "good")
- manager.queueMessage("Drop very much",
+ manager.queueMessage("Drop very much",
mixminion.Modules.DROP_TYPE, "")
queue = manager.queues['TestModule']
- self.failUnless(isinstance(queue,
+ self.failUnless(isinstance(queue,
mixminion.Modules.SimpleModuleDeliveryQueue))
self.assertEquals(3, queue.count())
self.assertEquals(exampleMod.processedMessages, [])
@@ -2549,9 +2735,9 @@
finally:
getLog().setMinSeverity(severity) #unsuppress warning
- #
+ #
# Try again, this time with the test module disabled.
- #
+ #
cfg_test = SERVER_CONFIG_SHORT + """
Homedir = %s
ModulePath = %s
@@ -2584,10 +2770,11 @@
# FFFF Add tests for catching exceptions from buggy modules
+
#----------------------------------------------------------------------
import mixminion.ServerMain
-#XXXX DOC
+# Sample server configuration to test ServerMain.
SERVERCFG = """
[Server]
Homedir: %(home)s
@@ -2612,26 +2799,19 @@
resumeLog()
return mixminion.ServerMain.ServerKeyring(conf)
-_IDENTITY_KEY = None
-def _getIdentityKey():
- global _IDENTITY_KEY
- if _IDENTITY_KEY is None:
- _IDENTITY_KEY = _getKeyring().getIdentityKey()
- return _IDENTITY_KEY
-
class ServerMainTests(unittest.TestCase):
def testServerKeyring(self):
keyring = _getKeyring()
home = _FAKE_HOME
# Test creating identity key
- identity = _getIdentityKey()
+ identity = keyring.getIdentityKey()
fn = os.path.join(home, "keys", "identity.key")
identity2 = mixminion.Crypto.pk_PEM_load(fn)
self.assertEquals(mixminion.Crypto.pk_get_modulus(identity),
mixminion.Crypto.pk_get_modulus(identity2))
# (Make sure warning case can occur.)
- pk = _ml.rsa_generate(128, 65537)
+ pk = getRSAKey(0,128)
mixminion.Crypto.pk_PEM_save(pk, fn)
suspendLog()
keyring.getIdentityKey()
@@ -2672,15 +2852,14 @@
self.assertEquals(4, len(keyring.keyIntervals))
keyring.removeDeadKeys()
self.assertEquals(3, len(keyring.keyIntervals))
- getLog().info("foo")
- if 0:
+ if USE_SLOW_MODE:
# These are slow, since they regenerate the DH params.
# Test getDHFile
f = keyring.getDHFile()
f2 = keyring.getDHFile()
self.assertEquals(f, f2)
-
+
# Test getTLSContext
keyring.getTLSContext()
@@ -2707,7 +2886,6 @@
dc = mixminion.ClientMain.DirectoryCache(dirname)
dc.load()
-
#----------------------------------------------------------------------
def testSuite():
suite = unittest.TestSuite()
@@ -2718,8 +2896,6 @@
suite.addTest(tc(BuildMessageTests))
return suite
- suite.addTest(tc(ClientMainTests))
- suite.addTest(tc(ServerMainTests))
suite.addTest(tc(MiscTests))
suite.addTest(tc(MinionlibCryptoTests))
suite.addTest(tc(CryptoTests))
@@ -2732,10 +2908,14 @@
suite.addTest(tc(PacketHandlerTests))
suite.addTest(tc(QueueTests))
+ suite.addTest(tc(ClientMainTests))
+ suite.addTest(tc(ServerMainTests))
+
# These tests are slowest, so we do them last.
suite.addTest(tc(ModuleManagerTests))
suite.addTest(tc(ServerInfoTests))
suite.addTest(tc(MMTPTests))
+
return suite
def testAll(name, args):