[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[minion-cvs] More tests for everything.
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.seul.org:/tmp/cvs-serv13122/lib/mixminion
Modified Files:
BuildMessage.py Common.py Crypto.py Formats.py HashLog.py
ServerInfo.py ServerProcess.py benchmark.py test.py
Log Message:
More tests for everything.
BuildMessage and ServerProcess are now working: we support all of the
non-network-related algorithms and formats in the spec. We can build
messages and reply blocks, and (if we're a server) process them enough
to pass messages off to the next server. We handle extended headers,
swapping, everything.
Performance is good: on my Athlon XP1700+, the server core takes 8.5
ms for a non-swap message, and 9 ms for a swap message. Of this, I
believe around 95% is spent in the C encryption code.
Next up, more documentation and comments and code clean-up. After
that, get queues and persistent server information working.
Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -d -r1.3 -r1.4
--- BuildMessage.py 29 May 2002 22:51:58 -0000 1.3
+++ BuildMessage.py 31 May 2002 12:47:58 -0000 1.4
@@ -4,6 +4,7 @@
from mixminion.Formats import *
import mixminion.Crypto as Crypto
import mixminion.Modules as Modules
+import operator
__all__ = [ 'buildForwardMessage', 'buildReplyBlock', 'buildReplyMessage',
'buildStatelessReplyBlock' ]
@@ -12,21 +13,28 @@
"XXXX"
return _buildMessage(payload, exitType, exitInfo, path1, path2)
-def buildReplyMessage(payload, exitType, exitInfo, path1, replyBlock):
+def buildReplyMessage(payload, path1, replyBlock):
+ # Bad iface; shouldn't take a tuple
"XXXX"
- return _buildMessage(payload, exitType, exitInfo, path1, reply=replyBlock)
+ return _buildMessage(payload, None, None,
+ path1=path1,
+ reply=replyBlock)
# Bad interface: this shouldn't return a tuple.
-def buildReplyBlock(path, exitType, exitInfo, prng):
+def buildReplyBlock(path, exitType, exitInfo, secretPRNG=None):
"XXXX"
- secrets = [ prng.getBytes(SECRET_LEN) for node in path ]
- headers = _buildHeaders(path, secrets, exitType, exitInfo)
- return (headers, path[0]), secrets
+ if secretPRNG == None:
+ secretPRNG = Crypto.AESCounterPRNG()
+ secrets = [ secretPRNG.getBytes(SECRET_LEN) for node in path ]
+ header = _buildHeader(path, secrets, exitType, exitInfo,
+ paddingPRNG=Crypto.AESCounterPRNG())
+ return (header, path[0]), secrets
# Bad interface: userkey should only be None if we trust the final node
# a lot.
-def buildStatelessReplyBlock(path, prng, user, userKey=None, email=0):
+def buildStatelessReplyBlock(path, user, userKey=None, email=0):
"XXXX"
+ # COMMENT
if email:
assert userKey
seed = Crypto.trng(16)
@@ -34,103 +42,155 @@
tag = Crypto.ctr_crypt(seed,userKey)
else:
tag = seed
- if emal:
+ if email:
exitType = Modules.SMTP_TYPE
- exitInfo = SMTPInfo(user, "RTRN"+key).pack()
+ exitInfo = SMTPInfo(user, "RTRN"+tag).pack()
else:
exitType = Modules.LOCAL_TYPE
- exitInfo = LocalInfo(user, "RTRN"+key).pack()
+ exitInfo = LocalInfo(user, "RTRN"+tag).pack()
prng = Crypto.AESCounterPRNG(seed)
- return buildReplyBlock(path, exitType, exitInto, prng)
+ return buildReplyBlock(path, exitType, exitInfo, prng)[0]
#----------------------------------------------------------------------
+# needs a more informative name
def _buildMessage(payload, exitType, exitInfo,
- path1, path2=None, reply=None, prng=None, paranoia=0):
+ path1, path2=None, reply=None, paddingPRNG=None, paranoia=0):
"XXXX"
assert path2 or reply
- if prng == None:
- prng = Crypto.AESCounterPRNG()
+ assert not (path2 and reply)
+ if paddingPRNG == None:
+ paddingPRNG = Crypto.AESCounterPRNG()
# ???? Payload padding/sizing must be handled in spec.
if len(payload) < PAYLOAD_LEN:
- payload += prng.getBytes(PAYLOAD_LEN-len(payload))
+ payload += paddingPRNG.getBytes(PAYLOAD_LEN-len(payload))
+
+ nHops = len(path1)
+ if path2: nHops += len(path2)
if paranoia:
- secrets1 = [ Crypto.trng(SECRET_LEN) for node in path1 ]
- if path2: secrets2 = [ Crypto.trng(SECRET_LEN) for node in path2 ]
+ secretRNG = Crypto.TrueRNG(SECRET_LEN*len(nHops))
else:
- secrets1 = [ prng.getBytes(SECRET_LEN) for node in path1 ]
- if path2: secrets2 = [ prng.getBytes(SECRET_LEN) for node in path2 ]
+ secretRNG = paddingPRNG
- if path2:
- node = path2[0]
+ if reply:
+ path1exitnode = reply[1]
+ path2exit = None
+ reply = reply[0]
else:
- node = reply[1]
- info = IPV4Info(node.getAddr(), node.getPort(), node.getKeyID())
- headers1 = _buildHeaders(path1, secrets1, Modules.SWAP_FWD_TYPE, info,prng)
+ path1exitnode = path2[0]
+ path2exit = ( exitType, exitInfo )
+
+ path1exit = ( Modules.SWAP_FWD_TYPE,
+ path1exitnode.getRoutingInfo().pack() )
+
+ return _buildMessage_impl(payload, path1, path1exit, path2, path2exit,
+ reply, nHops, secretRNG, paddingPRNG)
+
+#needs a more informative name
+def _buildMessage_impl(payload, path1, path1exit, path2, path2exit,
+ reply, nHops, secretRNG, paddingRNG):
+ "XXXX"
+ # XXXX ???? Payload padding/sizing must be handled in spec.
+ if len(payload) < PAYLOAD_LEN:
+ payload += paddingRNG.getBytes(PAYLOAD_LEN-len(payload))
+
+ secrets1 = [ secretRNG.getBytes(SECRET_LEN) for x in range(len(path1)) ]
+
if path2:
- headers2 = _buildHeaders(path2, secrets2, exitType, exitInfo, prng)
+ # Make secrets2 before header1 so we don't use the RNG to pad
+ # the first header yet.
+ secrets2 = [ secretRNG.getBytes(SECRET_LEN) for x in range(len(path2))]
+ header2 = _buildHeader(path2,secrets2,path2exit[0],path2exit[1],
+ paddingRNG)
else:
- headers2 = reply[0]
- return _constructMessage(secrets1, secrets2, headers1, headers2, payload)
+ secrets2 = None
+ header2 = reply
+ header1 = _buildHeader(path1,secrets1,path1exit[0],path1exit[1],
+ paddingRNG)
+ return _constructMessage(secrets1, secrets2, header1, header2, payload)
-def _buildHeaders(path, secrets, exitType, exitInfo, prng):
+def _buildHeader(path,secrets,exitType,exitInfo,paddingPRNG):
"XXXX"
- hops = len(path)
-
- #Calculate all routing info
+ # XXXX insist on sane parameters, path lengths.
routing = []
- for i in range(hops-1):
+ for i in range(len(path)-1):
nextNode = path[i+1]
- info = IPV4Info(nextNode.getAddr(), nextNode.getPort(),
- nextNode.getKeyID())
+ info = nextNode.getRoutingInfo()
routing.append( (Modules.FWD_TYPE, info.pack() ) )
+
+ routing.append( (exitType, exitInfo) )
- routing.append( (exitType, exitInfo) )
-
- # size[i] is size, in blocks, of headers for i.
- size = [ getTotalBlocksForRoutingInfoLen(len(info)) for t, info in routing]
+ return _buildHeader_impl(path,secrets,routing,paddingPRNG)
- totalSize = len(path)+size[-1]-1
+#routing is list of (type,info)
+def _buildHeader_impl(path, secrets, routing, paddingPRNG):
+ "XXXX"
+ hops = len(path)
- # Calculate masks, junk. mask[i] == the mask used by node i.
- # junkSeen[i]==the junk that node i will see, before encryption.
- #XXXX Comment better
- headerKeys = []
+ # sizes[i] is size, in blocks, of subheaders for i.
+ sizes =[ getTotalBlocksForRoutingInfoLen(len(info)) for t, info in routing]
+ # totalSize is number total number of blocks.
+ totalSize = reduce(operator.add, sizes)
+
+ # 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.
+ # junkSeen[i]==the junk that node i will see, before it does any
+ # encryption. Note that junkSeen[0]=="", because node 0
+ # sees no junk.
junkSeen = [ "" ]
- for secret, size in zip(secrets, size):
- ks = Crypto.Keyset(secret)
- headerKey = Crypto.aes_key(ks.get(Crypto.HEADER_SECRET_MODE))
- sz = size*128
- newJunk = Crypto.prng(ks.get(Crypto.RANDOM_JUNK_MODE),sz)
+ for secret, headerKey, size in zip(secrets, headerKeys, sizes):
+ # Here we're calculating the junk that node i+1 will see.
+ #
+ # Node i+1 sees the junk that node i saw, plus the junk that i appends,
+ # all encrypted by i.
+ prngKey = Crypto.Keyset(secret).get(Crypto.RANDOM_JUNK_MODE)
+ # NewJunk is the junk that node i will append. (It's as long as
+ # the subheaders that i removes.)
+ newJunk = Crypto.prng(prngKey,size*128)
nextJunk = junkSeen[-1] + newJunk
- nextJunk = Crypto.ctr_crypt(nextJunk, headerKey,
- HEADER_LEN-len(nextJunk))
+ # Node i encrypts starting with its first extended subheader. By
+ # the time it reaches the junk, it's traversed:
+ # All of its extended subheaders [(size-1)*128]
+ # The parts of the header that aren't junk.
+ # [HEADER_LEN-len(nextJunk)]
+ #
+ # Simplifying, we find that the PRNG index for the junk is
+ # HEADER_LEN-len(junkSeen[-1])-128*size+128*size-128
+ # = HEADER_LEN-len(junkSeen[-1])-128
+ startIdx = HEADER_LEN-len(junkSeen[-1])-128
+ nextJunk = Crypto.ctr_crypt(nextJunk, headerKey, startIdx)
junkSeen.append(nextJunk)
- headerKeys.append(headerKey)
-
- header = prng.getBytes(HEADER_LEN - totalSize*128)
- #XXXX comment better
+ # We start with the padding.
+ header = paddingPRNG.getBytes(HEADER_LEN - totalSize*128)
+
+ # Now, we build the subheaders, iterating through the nodes backwards.
for i in range(hops-1, -1, -1):
- rest = Crypto.ctr_crypt(header, headerKeys[i])
- digest = Crypto.sha1(rest+junkSeen[i])
- pubkey = Crypto.pk_from_modulus(path[i].getModulus())
rt, ri = routing[i]
subhead = Subheader(MAJOR_NO, MINOR_NO,
- secrets[i], digest,
- rt, ri).pack()
- esh = Crypto.pk_encrypt(subhead, pubkey)
+ secrets[i], " "*20,
+ rt, ri)
+
+ extHeaders = "".join(subhead.getExtraBlocks())
+ rest = Crypto.ctr_crypt("".join((extHeaders,header)), headerKeys[i])
+ subhead.digest = Crypto.sha1(rest+junkSeen[i])
+ pubkey = Crypto.pk_from_modulus(path[i].getModulus())
+ esh = Crypto.pk_encrypt(subhead.pack(), pubkey)
header = esh + rest
return header
-
+
# For a reply, secrets2==None
def _constructMessage(secrets1, secrets2, header1, header2, payload):
"XXXX"
+ #XXXX comment
assert len(payload) == PAYLOAD_LEN
assert len(header1) == len(header2) == HEADER_LEN
secrets1 = secrets1[:]
Index: Common.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Common.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- Common.py 29 May 2002 03:52:13 -0000 1.1
+++ Common.py 31 May 2002 12:47:58 -0000 1.2
@@ -4,5 +4,8 @@
class MixError(Exception):
pass
+class MixFatalError(MixError):
+ pass
+
Index: Crypto.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Crypto.py,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- Crypto.py 29 May 2002 22:51:58 -0000 1.4
+++ Crypto.py 31 May 2002 12:47:58 -0000 1.5
@@ -11,14 +11,14 @@
import sys
import mixminion._minionlib as _ml
-__all__ = [ 'init_crypto', 'sha1', 'ctr_crypt', 'prng',
+__all__ = [ 'init_crypto', 'sha1', 'ctr_crypt', 'prng', 'strxor',
'lioness_encrypt', 'lioness_decrypt', 'trng', 'pk_encrypt',
'pk_decrypt', 'pk_generate', 'openssl_seed',
'pk_get_modulus', 'pk_from_modulus',
'pk_encode_private_key', 'pk_decode_private_key',
'Keyset', 'AESCounterPRNG',
'HEADER_SECRET_MODE', 'PRNG_MODE', 'RANDOM_JUNK_MODE',
- 'HEADER_ENCRYPT_MODE',
+ 'HEADER_ENCRYPT_MODE', 'APPLICATION_KEY_MODE',
'PAYLOAD_ENCRYPT_MODE', 'HIDE_HEADER_MODE' ]
AES_KEY_LEN = 128/8
@@ -109,7 +109,6 @@
left = s[:20]
right = s[20:]
del s
- #XXXX This slice makes me nervous
left = _ml.strxor(left, _ml.sha1("".join([key4,right,key4])))
right = ctr_crypt(right, _ml.sha1("".join([key3,left,key3]))[:16])
left = _ml.strxor(left, _ml.sha1("".join([key2,right,key2])))
@@ -127,10 +126,7 @@
Returns (count) bytes of true random data from a true source of
entropy (/dev/urandom)"""
- f = open('/dev/urandom')
- d = f.read(count)
- f.close()
- return d
+ return _theTrueRNG.getBytes(count)
OAEP_PARAMETER = "He who would make his own liberty secure, "+\
"must guard even his enemy from oppression."
@@ -194,6 +190,7 @@
PAYLOAD_ENCRYPT_MODE = "PAYLOAD ENCRYPT"
HIDE_HEADER_MODE = "HIDE HEADER"
REPLAY_PREVENTION_MODE = "REPLAY PREVENTION"
+APPLICATION_KEY_MODE = "APPLICATION KEY"
class Keyset:
"""A Keyset represents a set of keys generated from a single master
@@ -230,20 +227,14 @@
#---------------------------------------------------------------------
-class AESCounterPRNG:
- _CHUNKSIZE = 16*1024
- _KEYSIZE = 16
- def __init__(self, seed=None):
- self.counter = 0
- self.bytes = ""
- if seed==None: seed=trng(AESCounterPRNG._KEYSIZE)
- self.key = _ml.aes_key(seed)
-
+class RNG:
+ def __init__(self, chunksize):
+ self.bytes=""
+ self.chunksize=chunksize
def getBytes(self, n):
if n > len(self.bytes):
- nMore = n+AESCounterPRNG._CHUNKSIZE-len(self.bytes)
- morebytes = prng(self.key,nMore,self.counter)
- self.counter+=nMore
+ nMore = n+self.chunksize-len(self.bytes)
+ morebytes = self._prng(nMore)
res = self.bytes+morebytes[:n-len(self.bytes)]
self.bytes=morebytes[n-len(self.bytes):]
return res
@@ -251,3 +242,31 @@
res = self.bytes[:n]
self.bytes=self.bytes[n:]
return res
+ def _prng(self, n):
+ assert 0
+
+class AESCounterPRNG(RNG):
+ def __init__(self, seed=None):
+ RNG.__init__(self, 16*1024)
+ self.counter = 0
+ if seed == None:
+ seed = trng(16)
+ self.key = aes_key(seed)
+ def _prng(self, n):
+ c = self.counter
+ self.counter+=n
+ return prng(self.key,n,c)
+
+def trng_uncached(n):
+ f = open('/dev/urandom')
+ d = f.read(n)
+ f.close()
+ return d
+
+class _TrueRNG(RNG):
+ def __init__(self,n):
+ RNG.__init__(self,n)
+ def _prng(self,n):
+ return trng_uncached(n)
+
+_theTrueRNG = _TrueRNG(128)
Index: Formats.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Formats.py,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -d -r1.3 -r1.4
--- Formats.py 29 May 2002 22:51:58 -0000 1.3
+++ Formats.py 31 May 2002 12:47:58 -0000 1.4
@@ -8,8 +8,8 @@
__all__ = [ 'ParseError', 'Message', 'Header', 'Subheader',
'parseMessage', 'parseHeader', 'parseSubheader',
'getTotalBlocksForRoutingInfoLen',
- 'IPV4Info', 'SMTPInfo',
- 'parseIPV4Info', 'parseSMTPInfo',
+ 'IPV4Info', 'SMTPInfo', 'LocalInfo',
+ 'parseIPV4Info', 'parseSMTPInfo', 'parseLocalInfo',
'ENC_SUBHEADER_LEN', 'HEADER_LEN',
'PAYLOAD_LEN', 'MAJOR_NO', 'MINOR_NO',
'SECRET_LEN']
@@ -116,8 +116,11 @@
if len(s) < MIN_SUBHEADER_LEN:
raise ParseError("Header too short")
- major, minor, secret, digest, rlen, rt = \
- struct.unpack(SH_UNPACK_PATTERN, s[:MIN_SUBHEADER_LEN])
+ try:
+ major, minor, secret, digest, rlen, rt = \
+ struct.unpack(SH_UNPACK_PATTERN, s[:MIN_SUBHEADER_LEN])
+ except struct.error:
+ raise ParseError("Misformatted subheader")
ri = s[MIN_SUBHEADER_LEN:]
if rlen < len(ri):
ri = ri[:rlen]
@@ -183,7 +186,9 @@
self.routinginfo = ("".join(raw))[:self.routinglen]
def pack(self):
- """Returns the (unencrypted) string representation of this Subhead"""
+ """Returns the (unencrypted) string representation of this Subhead.
+
+ Does not include extra blocks"""
assert self.routinglen == len(self.routinginfo)
assert len(self.digest) == DIGEST_LEN
assert len(self.secret) == SECRET_LEN
@@ -229,15 +234,21 @@
def _unpackIP(s):
"XXXX"
if len(s) != 4: raise ParseError("Malformed IP")
- return ".".join(map(str, struct.unpack("!BBBB", s)))
-
+ try:
+ return ".".join(map(str, struct.unpack("!BBBB", s)))
+ except struct.error:
+ raise ParseError("Misformatted IP address")
+
def parseIPV4Info(s):
"""parseIP4VInfo(s) -> IPV4Info
Converts routing info for an IPV4 address into an IPV4Info object."""
if len(s) != 4+2+DIGEST_LEN:
raise ParseError("IPV4 information with wrong length")
- ip, port, keyinfo = struct.unpack(IPV4_PAT, s)
+ try:
+ ip, port, keyinfo = struct.unpack(IPV4_PAT, s)
+ except struct.error:
+ raise ParseError("Misformatted IPV4 routing info")
ip = _unpackIP(ip)
return IPV4Info(ip, port, keyinfo)
@@ -274,10 +285,11 @@
def parseLocalInfo(s):
"XXXX"
- nil = s.find('\000')
- user = s[:nil]
- tag = s[nil+1]
- return LocalInfo(user,tag)
+ lst = s.split("\000",1)
+ if len(lst) == 1:
+ return LocalInfo(s,None)
+ else:
+ return LocalInfo(lst[0], lst[1])
class LocalInfo:
"XXXX"
@@ -287,4 +299,7 @@
self.tag = tag
def pack(self):
- return self.user+"\000"+self.tag
+ if self.tag:
+ return self.user+"\000"+self.tag
+ else:
+ return self.user
Index: HashLog.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/HashLog.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- HashLog.py 29 May 2002 03:52:13 -0000 1.1
+++ HashLog.py 31 May 2002 12:47:58 -0000 1.2
@@ -2,6 +2,7 @@
# $Id$
import anydbm
+from mixminion.Common import MixFatalError
__all__ = [ 'HashLog' ]
@@ -30,8 +31,7 @@
self.log = anydbm.open(filename, 'c')
try:
if self.log["KEYID"] != keyid:
- print "Mismatch on keyid"
- #XXXX Need warning mechanism
+ raise MixFatalError("Log KEYID does not match current KEYID")
except KeyError:
self.log["KEYID"] = keyid
Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- ServerInfo.py 29 May 2002 22:51:58 -0000 1.2
+++ ServerInfo.py 31 May 2002 12:47:58 -0000 1.3
@@ -1,15 +1,20 @@
# Copyright 2002 Nick Mathewson. See LICENSE for licensing information.
# $Id$
-#XXXX DOC
+"XXXX"
__all__ = [ 'ServerInfo' ]
+from mixminion.Modules import SWAP_FWD_TYPE, FWD_TYPE
+from mixminion.Formats import IPV4Info
+
#
# Stub class till we have the real thing
#
class ServerInfo:
+ "XXXX"
def __init__(self, addr, port, modulus, keyid):
+ "XXXX"
self.addr = addr
self.port = port
self.modulus = modulus
@@ -19,4 +24,7 @@
def getPort(self): return self.port
def getModulus(self): return self.modulus
def getKeyID(self): return self.keyid
+
+ def getRoutingInfo(self, swap=0):
+ return IPV4Info(self.addr, self.port, self.keyid)
Index: ServerProcess.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerProcess.py,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -d -r1.3 -r1.4
--- ServerProcess.py 29 May 2002 22:51:58 -0000 1.3
+++ ServerProcess.py 31 May 2002 12:47:58 -0000 1.4
@@ -3,14 +3,19 @@
import mixminion.Crypto as Crypto
import mixminion.Formats as Formats
+from mixminion.Formats import MAJOR_NO, MINOR_NO
import mixminion.Modules as Modules
import mixminion.Common as Common
+__all__ = [ 'ServerProcess', 'ContentError' ]
+
class ContentError(Common.MixError):
+ """XXXX"""
pass
class ServerProcess:
def __init__(self, privatekey, hashlog, exitHandler, forwardHandler):
+ """XXXX"""
self.privatekey = privatekey
self.hashlog = hashlog
self.exitHandler = exitHandler
@@ -18,83 +23,90 @@
# Raises ParseError, ContentError.
def processMessage(self, msg):
+ """XXXX"""
r = self._processMessage(msg)
if r != None:
m, a = r
- apply(m, a)
+ assert m in ("EXIT", "QUEUE")
+ if m == "EXIT":
+ self.exitHandler.handle(*a)
+ else:
+ self.forwardHandler.handle(*a)
# Raises ParseError, ContentError, SSLError.
- # Returns oneof (None), (method, argl)
+ # Returns oneof (None), (EXIT, argl), ("QUEUE", (ipv4info, msg))
def _processMessage(self, msg):
+ """XXXX"""
+ # XXXX Comment better
msg = Formats.parseMessage(msg)
header1 = Formats.parseHeader(msg.header1)
subh = header1[0]
subh = Crypto.pk_decrypt(subh, self.privatekey)
subh = Formats.parseSubheader(subh)
- if subh.major != 3 or subh.minor != 0:
+ if subh.major != MAJOR_NO or subh.minor != MINOR_NO:
raise ContentError("Invalid protocol version")
- # XXX Check digest, then decrypt the rest? Or decrypt the rest,
- # XXX then check the digest??? The spec is inconsistant.
digest = Crypto.sha1(header1[1:])
if digest != subh.digest:
raise ContentError("Invalid digest")
- # XXXX Need to decrypt extra routing info.
- if subh.isExtended():
- nExtra = subh.getNExtraBlocks()
- if nExtra > 15:
- raise ContentError("Impossibly long routing info length")
- extra = header1[1:1+nExtra]
- subh.appendExtraBlocks(extra)
- remainingHeader = header1[1+nExtra:]
- else:
- remainingHeader = header1[1:]
-
+ keys = Crypto.Keyset(subh.secret)
# Replay prevention
- keys = Crypto.Keyset(subh.master)
replayhash = keys.get(Crypto.REPLAY_PREVENTION_MODE, 20)
if self.hashlog.seenHash(replayhash):
raise ContentError("Duplicate message detected.")
else:
self.hashlog.logHash(replayhash)
-
- if type == Modules.DROP_TYPE:
+
+ rt = subh.routingtype
+ if rt == Modules.DROP_TYPE:
return None
+ header_sec_key = Crypto.aes_key(keys.get(Crypto.HEADER_SECRET_MODE))
+
+ if subh.isExtended():
+ nExtra = subh.getNExtraBlocks()
+ if (rt < Modules.MIN_EXIT_TYPE) or (nExtra > 15):
+ raise ContentError("Impossibly long routing info length")
+
+ extra = Crypto.ctr_crypt(header1[1:1+nExtra], header_sec_key)
+ subh.appendExtraBlocks(extra)
+ remainingHeader = header1[1+nExtra:]
+ else:
+ nExtra = 0
+ remainingHeader = header1[1:]
+
payload = Crypto.lioness_decrypt(msg.payload,
- keys.get(Crypto.PAYLOAD_ENCRYPT_MODE))
+ keys.getLionessKeys(Crypto.PAYLOAD_ENCRYPT_MODE))
- # XXXX This doesn't match what George said.
- if type > Modules.MIN_EXIT_TYPE:
- return (self.exitHandler.processMessage,
- (subh.routingtype, subh.routinginfo,
+ # XXXX This doesn't match what George said: it bails out too early.
+ # XXXX Also, it doesn't return the headers.
+ if rt >= Modules.MIN_EXIT_TYPE:
+ return ("EXIT",
+ (rt, subh.routinginfo,
keys.get(Crypto.APPLICATION_KEY_MODE),
payload))
- if type not in (SWAP_FWD_TYPE, FWD_TYPE):
+ if rt not in (Modules.SWAP_FWD_TYPE, Modules.FWD_TYPE):
raise ContentError("Unrecognized mixminion type")
remainingHeader = remainingHeader +\
Crypto.prng(keys.get(Crypto.PRNG_MODE),
- FORMATS.HEADER_LEN-len(remainingHeader))
- header1 = Crypto.ctr_crypt(remainingHeader,
- keys.get(Crypto.HEADER_SECRET_MODE))
-
+ Formats.HEADER_LEN-len(remainingHeader))
+
+ header1 = Crypto.ctr_crypt(remainingHeader, header_sec_key, nExtra*128)
+
header2 = Crypto.lioness_decrypt(msg.header2,
- keys.get(Crypto.HEADER_ENCRYPT_MODE))
+ keys.getLionessKeys(Crypto.HEADER_ENCRYPT_MODE))
- if type == Modules.SWAP_FWD_TYPE:
- hkey = Crypto.get_lioness_keys_from_payload(payload)
- header2 = Crypto.lioness_decrypt(msg.header2, hkey)
+ if rt == Modules.SWAP_FWD_TYPE:
+ hkey = Crypto.lioness_keys_from_payload(payload)
+ header2 = Crypto.lioness_decrypt(header2, hkey)
header1, header2 = header2, header1
address = Formats.parseIPV4Info(subh.routinginfo)
msg = Formats.Message(header1, header2, payload).pack()
- return (self.forwardHandler.queue, (address.ip,
- address.port,
- address.keyid,
- msg))
+ return ("QUEUE", (address, msg))
Index: benchmark.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/benchmark.py,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -d -r1.3 -r1.4
--- benchmark.py 29 May 2002 22:51:58 -0000 1.3
+++ benchmark.py 31 May 2002 12:47:58 -0000 1.4
@@ -2,15 +2,23 @@
# $Id$
from time import time
+__all__ = [ 'timeAll', 'testLeaks1', 'testLeaks2' ]
+
+PRECISION_FACTOR = 1
+
+primitives = {}#XXXXDOC
loop_overhead = {}
-def timeit_(fn, iters, ov=1):
+def timeit_(fn, iters, ov=1, p=None):
"""XXXX"""
+ iters *= PRECISION_FACTOR
nones = [None]*iters
overhead = [0, loop_overhead.get(iters, 0)][ov]
t = time()
for n in nones: fn()
t2 = time()-t
- return (t2-overhead) / float(iters)
+ t_each = (t2-overhead) / float(iters)
+ if p: primitives[p]=t_each
+ return t_each
min_o = 1.0
max_o = 0.0
@@ -31,9 +39,9 @@
else:
return "%f psec" % (t*1000000000L)
-def timeit(fn,times):
+def timeit(fn,times, p=None):
"""XXXX"""
- return timestr(timeit_(fn,times))
+ return timestr(timeit_(fn,times,p=p))
def spacestr(n):
if abs(n) < 1e4:
@@ -51,7 +59,6 @@
from Crypto import OAEP_PARAMETER
def cryptoTiming():
- loop_overhead = {}
short = "Hello, Dali!"
s1K = "8charstr"*128
s2K = s1K*2
@@ -64,8 +71,9 @@
print "Timing overhead: %s...%s" % (timestr(min_o),timestr(max_o))
print "SHA1 (short)", timeit((lambda : sha1(short)), 100000)
+ print "SHA1 (2K)", timeit((lambda : sha1(s8K)), 10000, p="SHA2")
print "SHA1 (8K)", timeit((lambda : sha1(s8K)), 10000)
- print "SHA1 (28K)", timeit((lambda : sha1(s28K)), 1000)
+ print "SHA1 (28K)", timeit((lambda : sha1(s28K)), 1000, p="SHA28")
print "SHA1 (32K)", timeit((lambda : sha1(s32K)), 1000)
shakey = "8charstr"*2
@@ -73,7 +81,7 @@
#print timeit((lambda : _ml.sha1(short,shakey)), 100000)
#print "Keyed SHA1 (8K)", timeit((lambda : _ml.sha1(s8K, shakey)), 10000)
#print "Keyed SHA1 (32K)", timeit((lambda : _ml.sha1(s32K, shakey)), 1000)
- print "Lioness-keyed SHA1 (28K, unoptimized)", timeit(
+ print "Keyed SHA1 for lioness (28K, unoptimized)", timeit(
(lambda : _ml.sha1("".join([shakey,s28K,shakey]))), 1000)
print "TRNG (20 byte)", timeit((lambda: trng(20)), 100)
@@ -86,7 +94,8 @@
key = "8charstr"*2
print "aes (short)", timeit((lambda: ctr_crypt(short,key)), 100000)
print "aes (1K)", timeit((lambda: ctr_crypt(s1K,key)), 10000)
- print "aes (28K)", timeit((lambda: ctr_crypt(s28K,key)), 100)
+ print "aes (2K)", timeit((lambda: ctr_crypt(s1K,key)), 10000, p="AES2")
+ print "aes (28K)", timeit((lambda: ctr_crypt(s28K,key)), 100, p="AES28")
print "aes (32K)", timeit((lambda: ctr_crypt(s32K,key)), 100)
key = _ml.aes_key(key)
@@ -99,17 +108,21 @@
(lambda: _ml.strxor(prng(key,32768),s32K)), 100)
print "prng (short)", timeit((lambda: prng(key,8)), 100000)
+ print "prng (128b)", timeit((lambda: prng(key,18)), 10000, p="PRNG128b")
print "prng (1K)", timeit((lambda: prng(key,1024)), 10000)
- print "prng (28K)", timeit((lambda: prng(key,28678)), 100)
+ print "prng (2K)", timeit((lambda: prng(key,1024)), 10000, p="PRNG2")
+ print "prng (28K)", timeit((lambda: prng(key,28678)), 100, p="PRNG28")
print "prng (32K)", timeit((lambda: prng(key,32768)), 100)
print "prng (32K, unoptimized)", timeit(
(lambda: ctr_crypt('\x00'*32768, key)), 100)
lkey = Keyset("keymaterial foo bar baz").getLionessKeys("T")
print "lioness E (1K)", timeit((lambda: lioness_encrypt(s1K, lkey)), 1000)
- print "lioness E (2K)", timeit((lambda: lioness_encrypt(s1K, lkey)), 1000)
+ print "lioness E (2K)", timeit((lambda: lioness_encrypt(s1K, lkey)), 1000,
+ p="LIONESS2")
print "lioness E (4K)", timeit((lambda: lioness_encrypt(s4K, lkey)), 1000)
- print "lioness E (28K)", timeit((lambda: lioness_encrypt(s28K, lkey)), 100)
+ print "lioness E (28K)", timeit((lambda: lioness_encrypt(s28K, lkey)), 100,
+ p="LIONESS28")
print "lioness E (32K)", timeit((lambda: lioness_encrypt(s32K, lkey)), 100)
print "lioness D (1K)", timeit((lambda: lioness_decrypt(s1K, lkey)), 1000)
print "lioness D (2K)", timeit((lambda: lioness_decrypt(s1K, lkey)), 1000)
@@ -127,30 +140,42 @@
print "RSA generate (1024 bit)", timeit((lambda: pk_generate()),10)
rsa = pk_generate()
print "Pad+RSA public encrypt",
- print timeit((lambda: pk_encrypt(s70b, rsa)),1000)
+ print timeit((lambda: pk_encrypt(s70b, rsa)),1000,p="RSAENC")
+
enc = pk_encrypt(s70b, rsa)
print "Pad+RSA private decrypt", timeit((lambda: pk_decrypt(enc, rsa)),100)
- for (bits,it) in ((2048,10),(4096,3)):
+ for (bits,it) in ((2048,10),(4096,10)):
+ t = time()
rsa2 = pk_generate(bits)
+ t = time()-t
+ print "RSA genrate (%d bit)"%bits, timestr(t)
enc = pk_encrypt(s70b, rsa2)
print "Pad+RSA private decrypt (%d bit)"%bits,
print timeit((lambda: pk_decrypt(enc, rsa2)),it)
#----------------------------------------------------------------------
def hashlogTiming():
+ import tempfile, os
+ print "#==================== HASH LOGS ======================="
for load in (100, 1000, 10000, 100000):
- _hashlogTiming(load)
+ fname = tempfile.mktemp(".db")
+ try:
+ _hashlogTiming(fname,load)
+ finally:
+ try:
+ os.unlink(fname)
+ except:
+ pass
-def _hashlogTiming(load):
- import tempfile, os
+def _hashlogTiming(fname, load):
+ import os
from mixminion.Crypto import AESCounterPRNG
from mixminion.HashLog import HashLog
prng = AESCounterPRNG("a"*16)
- fname = tempfile.mktemp(".db")
-
+
h = HashLog(fname, "A")
- hashes = [ prng.getBytes(20) for i in range(load) ]
+ hashes = [ prng.getBytes(20) for i in xrange(load) ]
t = time()
for hash in hashes:
@@ -164,7 +189,7 @@
t = time()-t
print "Check entry [hit] (%s entries)" %load, timestr( t/1000.0 )
- hashes =[ prng.getBytes(20) for i in range(1000) ]
+ hashes =[ prng.getBytes(20) for i in xrange(1000) ]
t = time()
for hash in hashes:
h.seenHash(hash)
@@ -173,9 +198,75 @@
h.close()
print "File size (%s entries)"%load, spacestr(os.stat(fname).st_size)
- os.unlink(fname)
#----------------------------------------------------------------------
+
+def buildMessageTiming():
+ import mixminion.BuildMessage as BMsg
+ from mixminion.ServerInfo import ServerInfo
+ print "#================= BUILD MESSAGE ====================="
+ pk = pk_generate()
+ payload = ("Junky qoph flags vext crwd zimb."*1024)[:22*1024]
+ serverinfo = [ ServerInfo("127.0.0.1", 48099, pk_get_modulus(pk),
+ "x"*20) ] * 16
+ def bh(np,it):
+ ctr = AESCounterPRNG()
+
+ tm = timeit_( \
+ lambda: BMsg._buildHeader(serverinfo[:np], ["Z"*16]*np,
+ 99, "Hello", ctr), it )
+
+ print "Build header (%s)" %(np), timestr(tm)
+
+ bh(1,100)
+ bh(4,40)
+ bh(8,20)
+ bh(16,10)
+
+ def bm(np1,np2,it):
+ tm = timeit_( \
+ lambda: BMsg.buildForwardMessage(payload,
+ 1,
+ "Hello",
+ serverinfo[:np1],
+ serverinfo[:np2]), it)
+ print "Build forward message (%sx%s)" %(np1,np2), timestr(tm)
+
+
+ bm(1,1,100)
+ bm(8,1,40)
+ bm(8,8,20)
+ bm(16,16,10)
+#----------------------------------------------------------------------
+
+def serverProcessTiming():
+ print "#================= SERVER PROCESS ====================="
+ from mixminion.ServerProcess import ServerProcess
+ from mixminion.ServerInfo import ServerInfo
+ from mixminion.BuildMessage import buildForwardMessage
+ from mixminion.Modules import SMTP_TYPE
+ class DummyLog:
+ def seenHash(self,h): return 0
+ def logHash(self,h): pass
+
+ pk = pk_generate()
+ n = pk_get_modulus(pk)
+ server = ServerInfo("127.0.0.1", 1, n, "X"*20)
+ sp = ServerProcess(pk, DummyLog(), None, None)
+
+ m_noswap = buildForwardMessage("Hello world", SMTP_TYPE, "f@invalid",
+ [server, server], [server, server])
+
+ print "Server process (no swap, no log)", timeit(
+ lambda : sp._processMessage(m_noswap), 100)
+
+ m_swap = buildForwardMessage("Hello world", SMTP_TYPE, "f@invalid",
+ [server], [server, server])
+
+ print "Server process (swap, no log)", timeit(
+ lambda : sp._processMessage(m_swap), 100)
+
+#----------------------------------------------------------------------
def testLeaks1():
print "Trying to leak (sha1,aes,xor,seed,oaep)"
s20k="a"*20*1024
@@ -235,7 +326,9 @@
def timeAll():
cryptoTiming()
+ buildMessageTiming()
hashlogTiming()
+ serverProcessTiming()
if __name__ == '__main__':
timeAll()
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- test.py 29 May 2002 22:51:58 -0000 1.4
+++ test.py 31 May 2002 12:47:58 -0000 1.5
@@ -3,134 +3,172 @@
import unittest
+def hexread(s):
+ r = []
+ hexvals = "0123456789ABCDEF"
+ for i in range(len(s) // 2):
+ v1 = hexvals.index(s[i*2])
+ v2 = hexvals.index(s[i*2+1])
+ c = (v1 << 4) + v2
+ assert 0 <= c < 256
+ r.append(chr(c))
+ return "".join(r)
+
#----------------------------------------------------------------------
import mixminion._minionlib as _ml
class MinionlibCryptoTests(unittest.TestCase):
- def hexread(self,s):
- r = []
- hexvals = "0123456789ABCDEF"
- for i in range(len(s) // 2):
- v1 = hexvals.index(s[i*2])
- v2 = hexvals.index(s[i*2+1])
- c = (v1 << 4) + v2
- assert 0 <= c < 256
- r.append(chr(c))
- return "".join(r)
-
+ """Tests for cryptographic C extensions."""
def test_sha1(self):
s1 = _ml.sha1
+ # A test vector from the SHA1 spec
self.assertEquals(s1("abc"),
- self.hexread("A9993E364706816ABA3E25717850C26C9CD0D89D"))
+ hexread("A9993E364706816ABA3E25717850C26C9CD0D89D"))
+ # Another test vector from the SHA1 spec
s = s1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")
self.assertEquals(s,
- self.hexread("84983E441C3BD26EBAAE4AA1F95129E5E54670F1"))
+ hexread("84983E441C3BD26EBAAE4AA1F95129E5E54670F1"))
+ # Make sure that we fail gracefully on non-string input.
self.failUnlessRaises(TypeError, s1, 1)
def test_xor(self):
xor = _ml.strxor
-
+
+ # Try a few known-value XORs.
self.assertEquals(xor("abc", "\000\000\000"), "abc")
self.assertEquals(xor("abc", "abc"), "\000\000\000")
self.assertEquals(xor("\xEF\xF0\x12", "\x11\x22\x35"), '\xFE\xD2\x27')
- # Make sure that the C doesn't (cringe) modify the strings.
+ # Make sure that the C doesn't (cringe) modify the strings out from
+ # under us.
a = "aaaa"
self.assertEquals(xor(a,"\000\000\000a"), "aaa\000")
self.assertEquals(a, "aaaa")
self.assertEquals(xor("\000\000\000a",a), "aaa\000")
self.assertEquals(a, "aaaa")
-
+
+ # Check for error msg on XORing strings of unequal length.
self.failUnlessRaises(TypeError, xor, "a", "bb")
def test_aes(self):
crypt = _ml.aes_ctr128_crypt
- # One of the test vectors from AES.
+ # First, try one of the test vectors from the AES spec.
key = txt = "\x80" + "\x00" * 15
key = _ml.aes_key(key)
-
- expected = self.hexread("8EDD33D3C621E546455BD8BA1418BEC8")
+ expected = hexread("8EDD33D3C621E546455BD8BA1418BEC8")
self.failUnless(crypt(key, txt, 0) == expected)
self.failUnless(crypt(key, txt) == expected)
+
+ # Now, make sure that the counter implementation is sane.
self.failUnless(crypt(key, " "*100, 0)[1:] == crypt(key, " "*99, 1))
+ self.failUnless(crypt(key, " "*100, 0)[30:] == crypt(key, " "*70, 30))
+
+ # Counter mode is its own inverse
self.failUnless(crypt(key,crypt(key, " "*100, 0),0) == " "*100)
+ # Try a different key to be sure.
teststr = """I have seen the best ciphers of my generation
Destroyed by cryptanalysis, broken, insecure,
Implemented still in cryptographic libraries"""
-
key2 = _ml.aes_key("xyzz"*4)
self.assertEquals(teststr,crypt(key2,crypt(key2,teststr)))
- # PRNG mode
- expected2 = self.hexread("0EDD33D3C621E546455BD8BA1418BEC8")
+ # Try generating the same test vector again, but this time in PRNG
+ # mode
+ expected2 = hexread("0EDD33D3C621E546455BD8BA1418BEC8")
self.assertEquals(expected2, crypt(key, "", 0, len(expected2)))
+ # PRNG mode ignores input.
self.assertEquals(expected2, crypt(key, "Z", 0, len(expected2)))
+ # Try an offset with prng mode.
self.assertEquals(expected2[5:], crypt(key, "", 5, len(expected2)-5))
+ # Make sure that PRNG mode with negative count yields ""
+ self.assertEquals("", crypt(key,"",0,-1))
- # Failing cases
+ # Can't use a non-key object.
self.failUnlessRaises(TypeError, crypt, "a", teststr)
- self.failUnlessRaises(TypeError, crypt, "a"*17, teststr)
-
- self.assertEquals("", crypt(key,"",0,-1))
+ # Can't make a key from a short string...
+ self.failUnlessRaises(TypeError, _ml.aes_key, "a")
+ # ...or a long string.
+ self.failUnlessRaises(TypeError, _ml.aes_key, "a"*17)
def test_openssl_seed(self):
+ # Just try seeding openssl a couple of times, and make sure it
+ # doesn't crash.
_ml.openssl_seed("Hello")
_ml.openssl_seed("")
def test_oaep(self):
+ strxor = _ml.strxor
+ # Check_oaep inverts add_oaep successfully.
x = _ml.add_oaep_padding("A", "B", 128)
+
self.assertEquals("A",_ml.check_oaep_padding(x, "B", 128))
-
+
+ # 86 bytes can be used with size=128
_ml.add_oaep_padding("A"*86, "B",128)
+ # But 300 is too much,
self.failUnlessRaises(TypeError,
_ml.add_oaep_padding,"A"*300, "B", 128)
+ # And so is even 87.
self.failUnlessRaises(_ml.SSLError,
_ml.add_oaep_padding,"A"*87, "B", 128)
+ # Changing a character at the beginning keeps it from checking.
+ ch = strxor(x[0], '\x01')
self.failUnlessRaises(_ml.SSLError,
- _ml.check_oaep_padding,x[1:]+"Y","B",128)
+ _ml.check_oaep_padding,ch+x[1:],"B",128)
+ # Changing a character at the end keeps it from checking.
+ ch = strxor(x[-1], '\x01')
self.failUnlessRaises(_ml.SSLError,
- _ml.check_oaep_padding,x[:-1]+"Y","B",128)
+ _ml.check_oaep_padding,x[:-1]+ch,"B",128)
def test_rsa(self):
p = _ml.rsa_generate(1024, 65535)
def sslerr(*args): self.failUnlessRaises(_ml.SSLError, *args)
+ #For all of SIGN, CHECK_SIG, ENCRYPT, DECRYPT...
for pub1 in (0,1):
for enc1 in (0,1):
msg = "Now is the time for all anonymous parties"
x = _ml.add_oaep_padding(msg, "B", 128)
x2 = _ml.rsa_crypt(p, x, pub1, enc1);
+ # ...Encryption inverts decryption...
x3 = _ml.rsa_crypt(p, x2, [1,0][pub1], [1,0][enc1]);
self.failUnless(x3 == x)
+ # ...And oaep is preserved.
x4 = _ml.check_oaep_padding(x3, "B", 128)
self.failUnless(x4 == msg)
- # Too short
+ # Fail if there is not enough padding
self.failUnlessRaises(_ml.SSLError,_ml.rsa_crypt,p,"X",1,1)
- # Too long
+ # Fail if there is too much padding
self.failUnlessRaises(_ml.SSLError,_ml.rsa_crypt,p,x+"ZZZ",1,1)
+ ####
+ # Test key encoding
padhello = _ml.add_oaep_padding("Hello", "B", 128)
for public in (0,1):
+ #encode(decode(encode(x))) == x.
x = _ml.rsa_encode_key(p,public)
p2 = _ml.rsa_decode_key(x,public)
x3 = _ml.rsa_encode_key(p2,public)
self.assertEquals(x,x3)
+ # decode(encode(x)) encrypts the same as x.
self.assertEquals(_ml.rsa_crypt(p,padhello,public,1),
_ml.rsa_crypt(p2,padhello,public,1))
+ # encoding public keys to/from their moduli.
+ self.assertEquals(_ml.rsa_get_modulus_bytes(p),1024/8)
n,e = _ml.rsa_get_public_key(p)
p2 = _ml.rsa_make_public_key(n,e)
self.assertEquals((n,e), _ml.rsa_get_public_key(p2))
self.assertEquals(65535,e)
self.assertEquals(_ml.rsa_encode_key(p,1), _ml.rsa_encode_key(p,1))
- # Try private-key ops with public key
+ # Try private-key ops with public key p3.
p3 = _ml.rsa_decode_key(_ml.rsa_encode_key(p,1),1)
msg1 = _ml.rsa_crypt(p, padhello, 1,1)
msg2 = _ml.rsa_crypt(p, padhello, 1,1)
@@ -147,10 +185,13 @@
from mixminion.Crypto import *
class CryptoTests(unittest.TestCase):
+ """Tests for Python cryptographic library"""
def test_initcrypto(self):
init_crypto()
def test_wrappers(self):
+ # Test simple wrappers over _minionlib functionality. Mainly, just
+ # test that _ml.foo and foo do the same thing.
self.assertEquals(_ml.sha1("xyzzy"), sha1("xyzzy"))
k = _ml.aes_key("xyzy"*4)
self.assertEquals(_ml.aes_ctr128_crypt(k,"hello",0),
@@ -162,6 +203,7 @@
self.assertEquals(prng(k,100,0),prng(k,50,0)+prng(k,50,50))
def test_rsa(self):
+
eq = self.assertEquals
k512 = pk_generate(512)
k1024 = pk_generate()
@@ -169,9 +211,12 @@
eq(512/8, _ml.rsa_get_modulus_bytes(k512))
eq(1024/8, _ml.rsa_get_modulus_bytes(k1024))
+ # Check pk_get_modulus sanity
self.failUnless((1L<<511) < pk_get_modulus(k512) < (1L<<513))
self.failUnless((1L<<1023) < pk_get_modulus(k1024) < (1L<<1024))
+ # Make sure that public keys can be made from moduli, and used to
+ # encrypt and decrypt.
msg="Good hello"
pub512 = pk_from_modulus(pk_get_modulus(k512))
pub1024 = pk_from_modulus(pk_get_modulus(k1024))
@@ -181,22 +226,28 @@
eq(msg, pk_decrypt(pk_encrypt(msg, k1024),k1024))
eq(msg, pk_decrypt(pk_encrypt(msg, pub1024),k1024))
+ # Make sure that CH_OAEP(RSA( )) inverts pk_encrypt.
eq(msg, _ml.check_oaep_padding(
_ml.rsa_crypt(k512, pk_encrypt(msg,k512), 0, 0),
mixminion.Crypto.OAEP_PARAMETER, 64))
+ # Make sure we can still encrypt after we've encoded/decoded a
+ # key.
encoded = pk_encode_private_key(k512)
decoded = pk_decode_private_key(encoded)
eq(msg, pk_decrypt(pk_encrypt(msg, pub512),decoded))
def test_trng(self):
+ # Make sure that the true rng is at least superficially ok.
self.assertNotEquals(trng(40), trng(40))
def test_lioness(self):
enc = lioness_encrypt
dec = lioness_decrypt
+
+ # Check basic cipher properties.
key = ("ABCDE"*4,) *4
- plain = mixminion.Crypto.OAEP_PARAMETER*100
+ plain = "The more it snows the more it goes on snowing"*10
self.assertNotEquals(plain, enc(plain,key))
self.assertNotEquals(plain, dec(plain,key))
self.assertEquals(len(plain), len(enc(plain,key)))
@@ -204,7 +255,24 @@
self.assertEquals(plain, dec(enc(plain,key),key))
self.assertEquals(plain, enc(dec(plain,key),key))
- #XXXX check for correct values
+ # Walk through a LIONESS encryption to check for correct values.
+ # Check getLionessKeys too.
+ s = "ABCDE"*4
+ key1 = sha1(s+"foo")
+ key2 = key1[:-1]+strxor(key1[-1], chr(1))
+ key3 = key1[:-1]+strxor(key1[-1], chr(2))
+ key4 = key1[:-1]+strxor(key1[-1], chr(3))
+
+ left = plain[:20]
+ right = plain[20:]
+ right = ctr_crypt( right, sha1(key1+left+key1)[:16] )
+ left = strxor(left, sha1(key2+right+key2))
+ right = ctr_crypt( right, sha1(key3+left+key3)[:16] )
+ left = strxor(left, sha1(key4+right+key4))
+
+ key = (key1,key2,key3,key4)
+ self.assertEquals(left+right, lioness_encrypt(plain,key))
+ self.assertEquals(key, Keyset("ABCDE"*4).getLionessKeys("foo"))
def test_keyset(self):
s = sha1
@@ -220,6 +288,7 @@
k.getLionessKeys("Baz"))
def test_aesprng(self):
+ # Make sure that AESCounterPRNG is really repeatable.
key ="aaaa"*4
PRNG = AESCounterPRNG(key)
self.assert_(prng(key,100000) == (
@@ -294,10 +363,18 @@
self.assertEquals(s.pack(), expected)
self.assertEquals(s.getExtraBlocks(), extra)
- #XXXX Need failing tests, routinginfo tests.
+ self.failUnlessRaises(ParseError,
+ parseSubheader, "a"*(41))
def test_headers(self):
- pass #XXXX
+ header = ("abcdefghi"*(256))[:2048]
+ h = parseHeader(header)
+ self.failUnless(h[0] == header[:128])
+ self.failUnless(h[4] == header[128*4:128*5])
+ self.failUnless(h[:1] == h[0])
+ self.failUnless(h[1:] == header[128:])
+ self.failUnless(h[1:4] == header[128:128*4])
+ self.failUnless(h[15] == header[-128:])
def test_message(self):
# 9 is relatively prime to all pwrs of 2.
@@ -307,16 +384,46 @@
self.assert_(msg.header1 == m[:2048])
self.assert_(msg.header2 == m[2048:4096])
self.assert_(msg.payload == m[4096:])
- # FAILING CASES XXXX
+ self.failUnlessRaises(ParseError, parseMessage, m[:-1])
+ self.failUnlessRaises(ParseError, parseMessage, m+"x")
def test_ipv4info(self):
- pass #XXXX
+ ri = hexread("12F400BCBBE30011223344556677889900112233445566778899")
+ inf = parseIPV4Info(ri)
+ self.assertEquals(inf.ip, "18.244.0.188")
+ self.assertEquals(inf.port, 48099)
+ self.assertEquals(inf.keyinfo, ri[-20:])
+ self.assertEquals(len(inf.pack()), 26)
+ self.assertEquals(inf.pack(), ri)
+ self.assertEquals(IPV4Info("18.244.0.188", 48099, ri[-20:]).pack(),
+ ri)
- def test_smtpinfo(self):
- pass #XXXX
+ self.failUnlessRaises(ParseError, parseIPV4Info, ri[:-1])
+ self.failUnlessRaises(ParseError, parseIPV4Info, ri+"x")
- def test_localinfo(self):
- pass #XXXX
+ def test_smtpinfolocalinfo(self):
+ for _class, _parse, _key in ((SMTPInfo, parseSMTPInfo, 'email'),
+ (LocalInfo, parseLocalInfo, 'user')):
+ ri = "no-such-user@wangafu.net\x00xyzzy"
+ inf = _parse(ri)
+ self.assertEquals(getattr(inf,_key), "no-such-user@wangafu.net")
+ self.assertEquals(inf.tag, "xyzzy")
+ self.assertEquals(inf.pack(), ri)
+ inf = _class("no-such-user@wangafu.net","xyzzy")
+ self.assertEquals(inf.pack(), ri)
+ # No tag
+ ri = "no-such-user@wangafu.net"
+ inf = _parse(ri)
+ self.assertEquals(inf.tag, None)
+ self.assertEquals(getattr(inf,_key), 'no-such-user@wangafu.net')
+ self.assertEquals(inf.pack(), ri)
+ # NUL in tag
+ ri = "no-such-user@wangafu.net\x00xyzzy\x00plover"
+ inf = _parse(ri)
+ self.assertEquals(getattr(inf,_key), "no-such-user@wangafu.net")
+ self.assertEquals(inf.tag, "xyzzy\x00plover")
+ self.assertEquals(inf.pack(), ri)
+
#----------------------------------------------------------------------
from mixminion.HashLog import HashLog
@@ -392,26 +499,37 @@
#----------------------------------------------------------------------
import mixminion.BuildMessage
+from mixminion.Modules import *
class FakePRNG:
def getBytes(self,n):
return "\x00"*n
-class BuildMessageTests(unittest.TestCase):
- def setUp(self):
- from ServerInfo import ServerInfo
+class _BMTSupport:
+ def __init__(self):
+ # We do this trick to keep from re-generating the keypairs
+ # for every message test.
self.pk1 = pk_generate()
self.pk2 = pk_generate()
self.pk3 = pk_generate()
- self.n_1 = pk_get_modulus(self.pk1)
- self.n_2 = pk_get_modulus(self.pk2)
- self.n_3 = pk_get_modulus(self.pk3)
- self.server1 = ServerInfo("127.0.0.1", 1, self.n_1, "X"*20)
- self.server2 = ServerInfo("127.0.0.2", 3, self.n_2, "Z"*20)
- self.server3 = ServerInfo("127.0.0.3", 5, self.n_3, "Q"*20)
+
+BMTSupport = _BMTSupport()
+
+class BuildMessageTests(unittest.TestCase):
+ def setUp(self):
+ from mixminion.ServerInfo import ServerInfo
+ 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)
def test_buildheader_1hop(self):
- bhead = mixminion.BuildMessage._buildHeaders
+ bhead = mixminion.BuildMessage._buildHeader
head = bhead([self.server1], ["9"*16], 99, "Hi mom", AESCounterPRNG())
self.do_header_test(head,
@@ -421,7 +539,7 @@
("Hi mom",))
def test_buildheader_2hops(self):
- bhead = mixminion.BuildMessage._buildHeaders
+ bhead = mixminion.BuildMessage._buildHeader
# 2 hops
head = bhead([self.server1, self.server2],
["9"*16, "1"*16], 99, "Hi mom", AESCounterPRNG())
@@ -430,43 +548,93 @@
self.do_header_test(head,
(self.pk1, self.pk2),
["9"*16, "1"*16],
- (mixminion.Modules.FWD_TYPE, 99),
+ (FWD_TYPE, 99),
(ipv4("127.0.0.2",3,"Z"*20).pack(),
"Hi mom"))
def test_buildheader_3hops(self):
- bhead = mixminion.BuildMessage._buildHeaders
+ bhead = mixminion.BuildMessage._buildHeader
# 3 hops
secrets = ["9"*16, "1"*16, "z"*16]
head = bhead([self.server1, self.server2, self.server3], secrets,
99, "Hi mom", AESCounterPRNG())
pks = (self.pk1,self.pk2,self.pk3)
- rtypes = (mixminion.Modules.FWD_TYPE,
- mixminion.Modules.FWD_TYPE,
- 99)
+ rtypes = (FWD_TYPE, FWD_TYPE, 99)
rinfo = (mixminion.Formats.IPV4Info("127.0.0.2", 3, "Z"*20).pack(),
mixminion.Formats.IPV4Info("127.0.0.3", 5, "Q"*20).pack(),
"Hi mom")
self.do_header_test(head, pks, secrets, rtypes, rinfo)
def do_header_test(self, head, pks, secrets, rtypes, rinfo):
+ retsecrets = []
+ retinfo = []
+ if secrets == None:
+ secrets = [ None ] * len(pks)
self.assertEquals(len(head), mixminion.Formats.HEADER_LEN)
for pk, secret, rt, ri in zip(pks, secrets,rtypes,rinfo):
subh = mixminion.Formats.parseSubheader(pk_decrypt(head[:128], pk))
- self.assertEquals(subh.secret, secret)
+ if secret:
+ self.assertEquals(subh.secret, secret)
+ else:
+ secret = subh.secret
+ retsecrets.append(secret)
self.assertEquals(subh.major, mixminion.Formats.MAJOR_NO)
self.assertEquals(subh.minor, mixminion.Formats.MINOR_NO)
- self.assertEquals(subh.routingtype, rt)
- self.assertEquals(subh.routinginfo, ri)
+
self.assertEquals(subh.digest, sha1(head[128:]))
+ self.assertEquals(subh.routingtype, rt)
ks = Keyset(secret)
key = ks.get(HEADER_SECRET_MODE)
prngkey = ks.get(RANDOM_JUNK_MODE)
- head = ctr_crypt(head[128:]+prng(prngkey,128), key)
+ if not subh.isExtended():
+ if ri:
+ self.assertEquals(subh.routinginfo, ri)
+ self.assertEquals(subh.routinglen, len(ri))
+ else:
+ retinfo.append(subh.routinginfo)
+ size = 128
+ n = 0
+ else:
+ self.assert_(len(ri) > mixminion.Formats.MAX_ROUTING_INFO_LEN)
+ n = subh.getNExtraBlocks()
+ size = (1+n)*128
+ more = ctr_crypt(head[128:128+128*n], key)
+ subh.appendExtraBlocks(more)
+ if ri:
+ self.assertEquals(subh.routinginfo, ri)
+ self.assertEquals(subh.routinglen, len(ri))
+ else:
+ retinfo.append(subh.routinginfo)
+
+ head = ctr_crypt(head[size:]+prng(prngkey,size), key, 128*n)
+
+ if retinfo:
+ return retsecrets, retinfo
+ else:
+ return retsecrets
def test_extended_routinginfo(self):
- #XXXX!!!! Code doesn't work
- pass
+ bhead = mixminion.BuildMessage._buildHeader
+ bhead_impl = mixminion.BuildMessage._buildHeader_impl
+ secrets = ["9"*16 ]
+ longStr = "Foo"*50
+ head = bhead([self.server1 ], secrets, 99, longStr, AESCounterPRNG())
+ pks = (self.pk1,)
+ rtypes = (99,)
+ rinfo = (longStr,)
+
+ self.do_header_test(head, pks, secrets, rtypes, rinfo)
+
+ secrets.append("1"*16)
+ longStr2 = longStr*2
+
+ head = bhead_impl([self.server1,self.server2], secrets,
+ [ (99,longStr2) , (99,longStr) ], AESCounterPRNG())
+
+ pks = (self.pk1,self.pk2)
+ rtypes = (99,99)
+ rinfo = (longStr2,longStr)
+ self.do_header_test(head, pks, secrets, rtypes, rinfo)
def test_constructmessage(self):
consMsg = mixminion.BuildMessage._constructMessage
@@ -509,7 +677,8 @@
self.assert_(head2 == h2)
self.assert_(payload == pld)
- #### Reply case
+ ######
+ ### Reply case
message = consMsg(secrets1, None, h1, h2, pld)
self.assertEquals(len(message), mixminion.Formats.MESSAGE_LEN)
msg = mixminion.Formats.parseMessage(message)
@@ -527,21 +696,278 @@
self.assert_(head2 == h2)
self.assert_(payload == pld)
+
+ def do_message_test(self, msg,
+ header_info_1, #XXXX doc
+ header_info_2,
+ payload):
+ # Check header 1, and get secrets
+ sec = self.do_header_test(msg[:2048], *header_info_1)
+ h2 = msg[2048:4096]
+ p = msg[4096:]
+ # Do decryption steps for header 1.
+ for s in sec:
+ ks = Keyset(s)
+ p = lioness_decrypt(p,ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE))
+ h2 = lioness_decrypt(h2,ks.getLionessKeys(HEADER_ENCRYPT_MODE))
+ h2 = lioness_decrypt(h2,mixminion.Crypto.lioness_keys_from_payload(p))
+
+ sec = self.do_header_test(h2, *header_info_2)
+ for s in sec:
+ ks = Keyset(s)
+ p = lioness_decrypt(p,ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE))
+
+ # XXXX Need to do something about size encoding.
+ self.assertEquals(payload, p[:len(payload)])
+
- def test_buildmessage(self):
- #XXXX
- pass
+ def test_build_fwd_message(self):
+ bfm = mixminion.BuildMessage.buildForwardMessage
+ payload = "Hello"
+
+ m = bfm(payload, 99, "Goodbye",
+ [self.server1, self.server2],
+ [self.server3, self.server2])
+
+ self.do_message_test(m,
+ ( (self.pk1, self.pk2), None,
+ (FWD_TYPE, SWAP_FWD_TYPE),
+ (self.server2.getRoutingInfo().pack(),
+ self.server3.getRoutingInfo().pack()) ),
+ ( (self.pk3, self.pk2), None,
+ (FWD_TYPE, 99),
+ (self.server2.getRoutingInfo().pack(),
+ "Goodbye") ),
+ "Hello" )
+
+ m = bfm(payload, 99, "Goodbye",
+ [self.server1, ],
+ [self.server3, ])
+
+ self.do_message_test(m,
+ ( (self.pk1,), None,
+ (SWAP_FWD_TYPE, ),
+ ( self.server3.getRoutingInfo().pack(), ) ),
+ ( (self.pk3, ), None,
+ (99,),
+ ("Goodbye",) ),
+ "Hello" )
def test_buildreply(self):
- #XXXX
- pass
+ brb = mixminion.BuildMessage.buildReplyBlock
+ bsrb = mixminion.BuildMessage.buildStatelessReplyBlock
+ brm = mixminion.BuildMessage.buildReplyMessage
- def test_buildstatelessreply(self):
- #XXXX
- pass
+ ## Stateful reply blocks.
+ (rb, node1), secrets = \
+ brb([ self.server3, self.server1, self.server2,
+ self.server1, self.server3 ],
+ SMTP_TYPE,
+ SMTPInfo("no-such-user@invalid", None).pack())
+ pks_1 = (self.pk3, self.pk1, self.pk2, self.pk1, self.pk3)
+ infos = (self.server1.getRoutingInfo().pack(),
+ self.server2.getRoutingInfo().pack(),
+ self.server1.getRoutingInfo().pack(),
+ self.server3.getRoutingInfo().pack())
+
+ self.assert_(node1 is self.server3)
+
+ m = brm("Information?",
+ [self.server3, self.server1],
+ (rb,node1))
+
+ self.do_message_test(m,
+ ((self.pk3, self.pk1), None,
+ (FWD_TYPE,SWAP_FWD_TYPE),
+ (self.server1.getRoutingInfo().pack(),
+ self.server3.getRoutingInfo().pack())),
+ (pks_1, secrets,
+ (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,SMTP_TYPE),
+ infos+(
+ SMTPInfo("no-such-user@invalid",None).pack(),
+ )),
+ "Information?")
+
+ ## Stateless replies
+ rb,node1 = bsrb([ self.server3, self.server1, self.server2,
+ self.server1, self.server3 ],
+ "fred", "Galaxy Far Away.", 1)
+
+ sec,(loc,) = self.do_header_test(rb, pks_1, None,
+ (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,SMTP_TYPE),
+ infos+(None,))
+ s = "fred\x00RTRN"
+ self.assert_(loc.startswith(s))
+ seed = ctr_crypt(loc[len(s):], "Galaxy Far Away.")
+ prng = AESCounterPRNG(seed)
+ self.assert_(sec == [ prng.getBytes(16) for i in range(5)])
+
+ ## Stateless reply, no user key (trusted server)
+ rb,node1 = bsrb([ self.server3, self.server1, self.server2,
+ self.server1, self.server3 ],
+ "fred" )
+ sec,(loc,) = self.do_header_test(rb, pks_1, None,
+ (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,LOCAL_TYPE),
+ infos+(None,))
+ self.assert_(loc.startswith(s))
+ seed = loc[len(s):]
+ prng = AESCounterPRNG(seed)
+ self.assert_(sec == [ prng.getBytes(16) for i in range(5)])
#----------------------------------------------------------------------
-import mixminion.ServerProcess
+# Having tested BuildMessage without using ServerProcess, we can now use
+# BuildMessage to see whether ServerProcess is doing the right thing.
+#
+# (of course, we still need to build failing messages by hand)
+
+class ServerProcessTests(unittest.TestCase):
+ def setUp(self):
+ from mixminion.ServerProcess import ServerProcess
+ from mixminion.ServerInfo import ServerInfo
+ from tempfile import mktemp
+ self.pk1 = BMTSupport.pk1
+ self.pk2 = BMTSupport.pk2
+ self.pk3 = BMTSupport.pk3
+ self.tmpfile = mktemp(".db")
+ 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.sp1 = ServerProcess(self.pk1, h, None, None)
+ self.sp2 = ServerProcess(self.pk2, h, None, None)
+ self.sp3 = ServerProcess(self.pk3, h, None, None)
+
+ def tearDown(self):
+ import os
+ try:
+ os.unlink(self.tmpfile)
+ except:
+ pass
+
+ def do_test_chain(self, m, sps, routingtypes, routinginfo, payload):
+ for sp, rt, ri in zip(sps,routingtypes,routinginfo):
+ res = sp._processMessage(m)
+ self.assertEquals(len(res), 2)
+ if rt in (FWD_TYPE, SWAP_FWD_TYPE):
+ self.assertEquals(res[0], "QUEUE")
+ self.assertEquals(res[1][0].pack(), ri)
+ self.assertEquals(FWD_TYPE, rt)
+ m = res[1][1]
+ else:
+ self.assertEquals(res[0], "EXIT")
+ self.assertEquals(res[1][0], rt)
+ self.assertEquals(res[1][1], ri)
+ # XXXX TEST application key
+ self.assert_(res[1][3].startswith(payload))
+ break
+
+ def test_successful(self):
+ bfm = mixminion.BuildMessage.buildForwardMessage
+ # A two-hop/one-hop message.
+ p = "Now is the time for all good men to come to the aid"
+ m = bfm(p, SMTP_TYPE, "nobody@invalid",
+ [self.server1, self.server2], [self.server3])
+
+ self.do_test_chain(m,
+ [self.sp1,self.sp2,self.sp3],
+ [FWD_TYPE, FWD_TYPE, SMTP_TYPE],
+ [self.server2.getRoutingInfo().pack(),
+ self.server3.getRoutingInfo().pack(),
+ "nobody@invalid"],
+ p)
+
+ # A one-hop/one-hop message.
+ m = bfm(p, SMTP_TYPE, "nobody@invalid",
+ [self.server1], [self.server3])
+
+ self.do_test_chain(m,
+ [self.sp1,self.sp3],
+ [FWD_TYPE, SMTP_TYPE],
+ [self.server3.getRoutingInfo().pack(),
+ "nobody@invalid"],
+ p)
+
+ # A 3/3 message with a long exit header.
+ for i in (100,300):
+ longemail = "f"*i+"@invalid"
+ m = bfm(p, SMTP_TYPE, longemail,
+ [self.server1, self.server2, self.server1],
+ [self.server3, self.server1, self.server2])
+
+ self.do_test_chain(m,
+ [self.sp1,self.sp2,self.sp1,
+ self.sp3,self.sp1,self.sp2],
+ [FWD_TYPE,FWD_TYPE,FWD_TYPE,
+ FWD_TYPE,FWD_TYPE,SMTP_TYPE],
+ [self.server2.getRoutingInfo().pack(),
+ self.server1.getRoutingInfo().pack(),
+ self.server3.getRoutingInfo().pack(),
+ self.server1.getRoutingInfo().pack(),
+ self.server2.getRoutingInfo().pack(),
+ longemail],
+ p)
+
+
+ def test_rejected(self):
+ bfm = mixminion.BuildMessage.buildForwardMessage
+ brm = mixminion.BuildMessage.buildReplyMessage
+ brb = mixminion.BuildMessage.buildReplyBlock
+ from mixminion.ServerProcess import ContentError
+
+ # A long intermediate header needs to fail.
+ from mixminion.ServerInfo import ServerInfo
+ server1X = ServerInfo("127.0.0.1", 1, pk_get_modulus(self.pk1), "X"*20)
+ class _packable:
+ def pack(self): return "x"*200
+ server1X.getRoutingInfo = lambda : _packable()
+
+ m = bfm("Z", LOCAL_TYPE, "hello\000bye",
+ [self.server2, server1X, self.server3],
+ [server1X, self.server2, self.server3])
+ self.failUnlessRaises(ContentError, self.sp2._processMessage, m)
+
+ # Duplicate messages need to fail.
+ m = bfm("Z", SMTP_TYPE, "nobody@invalid",
+ [self.server1, self.server2], [self.server3])
+ self.sp1._processMessage(m)
+ self.failUnlessRaises(ContentError, self.sp1._processMessage, m)
+
+ # Duplicate reply blocks need to fail
+ (r,n),s = brb([self.server3], SMTP_TYPE, "fred@invalid")
+ m = brm("Y", [self.server2], (r,n))
+ m2 = brm("Y", [self.server1], (r,n))
+ q, (a,m) = self.sp2._processMessage(m)
+ self.sp3._processMessage(m)
+ q, (a,m2) = self.sp1._processMessage(m2)
+ self.failUnlessRaises(ContentError, self.sp3._processMessage, m2)
+
+ # Even duplicate secrets need to go.
+ prng = AESCounterPRNG(" "*16)
+ (r1,n),s = brb([self.server1], SMTP_TYPE, "fred@invalid",prng)
+ prng = AESCounterPRNG(" "*16)
+ (r2,n),s = brb([self.server2], LOCAL_TYPE, "foo",prng)
+ m = brm("Y", [self.server3], (r1,n))
+ m2 = brm("Y", [self.server3], (r2,n))
+ q, (a,m) = self.sp3._processMessage(m)
+ self.sp1._processMessage(m)
+ q, (a,m2) = self.sp3._processMessage(m2)
+ self.failUnlessRaises(ContentError, self.sp2._processMessage, m2)
+
+ # Drop gets dropped.
+ m = bfm("Z", DROP_TYPE, "", [self.server2], [self.server2])
+ q, (a,m) = self.sp2._processMessage(m)
+ res = self.sp2._processMessage(m)
+ self.assertEquals(res,None)
+
+ # XXXX Bogus types
+ # XXXX Non-parsing information
+ # XXXX Bad Major or Minor
+ # XXXX Bad digest
+ # XXXX Bad payload
+
#----------------------------------------------------------------------
def testSuite():
@@ -553,6 +979,7 @@
suite.addTest(tc(FormatTests))
suite.addTest(tc(HashLogTests))
suite.addTest(tc(BuildMessageTests))
+ suite.addTest(tc(ServerProcessTests))
return suite
def testAll():