[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[minion-cvs]



Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.seul.org:/tmp/cvs-serv18497/lib/mixminion

Modified Files:
	BuildMessage.py Crypto.py Formats.py ServerInfo.py 
	ServerProcess.py benchmark.py test.py 
Log Message:


Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- BuildMessage.py	29 May 2002 17:46:23 -0000	1.2
+++ BuildMessage.py	29 May 2002 22:51:58 -0000	1.3
@@ -9,13 +9,16 @@
             'buildStatelessReplyBlock' ]
 
 def buildForwardMessage(payload, exitType, exitInfo, path1, path2):
+    "XXXX"
     return _buildMessage(payload, exitType, exitInfo, path1, path2)
 
 def buildReplyMessage(payload, exitType, exitInfo, path1, replyBlock):
+    "XXXX"
     return _buildMessage(payload, exitType, exitInfo, path1, reply=replyBlock)
 
 # Bad interface: this shouldn't return a tuple. 
 def buildReplyBlock(path, exitType, exitInfo, prng):
+    "XXXX"
     secrets = [ prng.getBytes(SECRET_LEN) for node in path ]
     headers = _buildHeaders(path, secrets, exitType, exitInfo)
     return (headers, path[0]), secrets
@@ -23,6 +26,7 @@
 # Bad interface: userkey should only be None if we trust the final node
 # a lot.
 def buildStatelessReplyBlock(path, prng, user, userKey=None, email=0):
+    "XXXX"
     if email:
         assert userKey
     seed = Crypto.trng(16)
@@ -43,6 +47,7 @@
 #----------------------------------------------------------------------
 def _buildMessage(payload, exitType, exitInfo,
                   path1, path2=None, reply=None, prng=None, paranoia=0):
+    "XXXX"
     assert path2 or reply
     if prng == None:
         prng = Crypto.AESCounterPRNG()
@@ -62,7 +67,7 @@
         node = path2[0]
     else:
         node = reply[1]
-    info = IPV4Info(node.getIP(), node.getPort(), node.getKeyID())
+    info = IPV4Info(node.getAddr(), node.getPort(), node.getKeyID())
     headers1 = _buildHeaders(path1, secrets1, Modules.SWAP_FWD_TYPE, info,prng)
     if path2:
         headers2 = _buildHeaders(path2, secrets2, exitType, exitInfo, prng)
@@ -72,76 +77,82 @@
 
 
 def _buildHeaders(path, secrets, exitType, exitInfo, prng):
+    "XXXX"
     hops = len(path)
 
     #Calculate all routing info
     routing = []
     for i in range(hops-1):
         nextNode = path[i+1]
-        info = IPV4Info(nextNode.getIP(), nextNode.getPort(),
+        info = IPV4Info(nextNode.getAddr(), nextNode.getPort(),
                         nextNode.getKeyID())
         routing.append( (Modules.FWD_TYPE, info.pack() ) )
     
     routing.append( (exitType, exitInfo) )                   
     
     # size[i] is size, in blocks, of headers for i.
-    size = [ getTotalBlocksForRoutingInfo(info) for t, info in routing ]
+    size = [ getTotalBlocksForRoutingInfoLen(len(info)) for t, info in routing]
 
-    totalSize = len(path)+size[-1]-1 
+    totalSize = len(path)+size[-1]-1
 
-    # Calculate masks, junk.
-    masks = []
-    junk = [ "" ]
-    headersecrets = []
+    # 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 = []
+    junkSeen = [ "" ]
     for secret, size in zip(secrets, size):
-        ks = Crypto.Keyset(secrets)
-        hs = ks.get(Crypto.HEADER_SECRET_MODE)
-        nextMask = Crypto.prng(hs, HEADER_LEN)
-        nextJunk = junk[-1] + Crypto.prng(ks.get(Crypto.RANDOM_JUNK_MODE),size)
-        nextJunk = Crypto.strxor(nextJunk, nextMask[HEADER_LEN-len(nextJunk):])
-        junk.append(nextJunk)
-        masks.append(nextMask)
-        headersecrets.append(hs)
+        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)
+        nextJunk = junkSeen[-1] + newJunk
+        nextJunk = Crypto.ctr_crypt(nextJunk, headerKey,
+                                    HEADER_LEN-len(nextJunk))
+        junkSeen.append(nextJunk)
+        headerKeys.append(headerKey)
         
-    del junk[0]
-    
     header = prng.getBytes(HEADER_LEN - totalSize*128)
-    
+
+    #XXXX comment better
     for i in range(hops-1, -1, -1):
-        jnk = junk[i]
-        rest = Crypto.strxor(header, masks[i])
-        digest = Crypto.sha1(rest+junk[i])
-        pubkey = Crypto.pk_from_modulus(nodes[i].getModulus())
+        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[i],
+                            secrets[i], digest,
                             rt, ri).pack()
-        esh = Crypto.pk_encrypt(pubkey, subhead)
-        header = subhead + rest
+        esh = Crypto.pk_encrypt(subhead, pubkey)
+        header = esh + rest
 
     return header
-
  
 # For a reply, secrets2==None
 def _constructMessage(secrets1, secrets2, header1, header2, payload):
+    "XXXX"
     assert len(payload) == PAYLOAD_LEN
     assert len(header1) == len(header2) == HEADER_LEN
+    secrets1 = secrets1[:]
+    if secrets2:
+        secrets2 = secrets2[:]
     
     if secrets2:
         secrets2.reverse()
         for secret in secrets2:
-            key = Crypto.Keyset(secret).getLionessKeys(PAYLOAD_ENCRYPT_MODE)
-            payload = Crypto.lioness_encrypt(key, payload)
+            ks = Crypto.Keyset(secret)
+            key = ks.getLionessKeys(Crypto.PAYLOAD_ENCRYPT_MODE)
+            payload = Crypto.lioness_encrypt(payload, key)
 
-    key = Crypto.get_lioness_keys_from_payload(payload)
-    header2 = Crypto.lionesss_encrypt(key, header2)
+    key = Crypto.lioness_keys_from_payload(payload)
+    header2 = Crypto.lioness_encrypt(header2, key)
 
     secrets1.reverse()
     for secret in secrets1:
         ks = Crypto.Keyset(secret)
-        hkey = ks.getLionessKeys(HEADER_ENCRYPT_MODE)
-        pkey = ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE)
-        header2 = Crypto.lioness_encrypt(hkey, header2)
-        payload = Crypto.lioness_encrypt(pkey, payload)
+        hkey = ks.getLionessKeys(Crypto.HEADER_ENCRYPT_MODE)
+        pkey = ks.getLionessKeys(Crypto.PAYLOAD_ENCRYPT_MODE)
+        header2 = Crypto.lioness_encrypt(header2,hkey)
+        payload = Crypto.lioness_encrypt(payload,pkey)
 
     return Message(header1, header2, payload).pack()

Index: Crypto.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Crypto.py,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -d -r1.3 -r1.4
--- Crypto.py	29 May 2002 18:54:43 -0000	1.3
+++ Crypto.py	29 May 2002 22:51:58 -0000	1.4
@@ -17,7 +17,8 @@
             'pk_get_modulus', 'pk_from_modulus',
             'pk_encode_private_key', 'pk_decode_private_key',
             'Keyset', 'AESCounterPRNG',
-            'HEADER_SECRET_MODE', 'PRNG_MODE', 'HEADER_ENCRYPT_MODE',
+            'HEADER_SECRET_MODE', 'PRNG_MODE', 'RANDOM_JUNK_MODE',
+            'HEADER_ENCRYPT_MODE',
             'PAYLOAD_ENCRYPT_MODE', 'HIDE_HEADER_MODE' ]
 
 AES_KEY_LEN = 128/8
@@ -48,6 +49,12 @@
     strings' lengths are unequal."""
     return _ml.strxor(s1, s2)
 
+def aes_key(key):
+    """aes_key(key) ->  aes_key
+
+       Returns an opaque precomputation of the 16-byte AES key, key."""
+    return _ml.aes_key(key)
+
 def ctr_crypt(s, key, idx=0):
     """ctr_crypt(s, key, idx=0) -> str
 
@@ -182,7 +189,7 @@
 #----------------------------------------------------------------------
 
 HEADER_SECRET_MODE = "HEADER SECRET KEY"
-PRNG_MODE = "RANDOM JUNK"
+PRNG_MODE = RANDOM_JUNK_MODE = "RANDOM JUNK"
 HEADER_ENCRYPT_MODE = "HEADER ENCRYPT"
 PAYLOAD_ENCRYPT_MODE = "PAYLOAD ENCRYPT"
 HIDE_HEADER_MODE = "HIDE HEADER"

Index: Formats.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Formats.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- Formats.py	29 May 2002 18:54:43 -0000	1.2
+++ Formats.py	29 May 2002 22:51:58 -0000	1.3
@@ -7,7 +7,7 @@
 
 __all__ = [ 'ParseError', 'Message', 'Header', 'Subheader',
             'parseMessage', 'parseHeader', 'parseSubheader',
-            'getTotalBlocksForRoutingInfo',
+            'getTotalBlocksForRoutingInfoLen',
             'IPV4Info', 'SMTPInfo',
             'parseIPV4Info', 'parseSMTPInfo',
             'ENC_SUBHEADER_LEN', 'HEADER_LEN',
@@ -58,7 +58,7 @@
         
     return Message(s[:HEADER_LEN],
                    s[HEADER_LEN:HEADER_LEN*2],
-                   s[HEADER_LEN*2])
+                   s[HEADER_LEN*2:])
 
 class Message:
     """Represents a complete Mixminion packet
@@ -123,7 +123,7 @@
         ri = ri[:rlen]
     return Subheader(major,minor,secret,digest,rt,ri,rlen)
 
-def getTotalBlocksForRoutingInfo(bytes):
+def getTotalBlocksForRoutingInfoLen(bytes):
     if bytes <= MAX_ROUTING_INFO_LEN:
         return 1
     else:
@@ -167,7 +167,7 @@
     def getNExtraBlocks(self):
         """Returns the number of extra blocks that will be needed to fit
            the routinginfo."""
-        return getTotalBlocksForRoutingInfo(self.routinglen)-1
+        return getTotalBlocksForRoutingInfoLen(self.routinglen)-1
 
     def appendExtraBlocks(self, data):
         """appendExtraBlocks(str)
@@ -211,7 +211,25 @@
                 result.append(content)
             return result
 
-IPV4_PAT = "!H%ds" % DIGEST_LEN
+IPV4_PAT = "!4sH%ds" % DIGEST_LEN
+
+def _packIP(s):
+    "xxxx"
+    addr = s.split(".")
+    if len(addr) != 4:
+        raise ParseError("Malformed IP address")
+    try:
+        addr = map(int, addr)
+    except ValueError:
+        raise ParseError("Malformed IP address")
+    for i in addr:
+        if not (0 <= i <= 255): raise ParseError("Malformed IP address")
+    return struct.pack("!BBBB", *addr)
+
+def _unpackIP(s):
+    "XXXX"
+    if len(s) != 4: raise ParseError("Malformed IP")
+    return ".".join(map(str, struct.unpack("!BBBB", s)))
 
 def parseIPV4Info(s):
     """parseIP4VInfo(s) -> IPV4Info
@@ -220,6 +238,7 @@
     if len(s) != 4+2+DIGEST_LEN:
         raise ParseError("IPV4 information with wrong length")
     ip, port, keyinfo = struct.unpack(IPV4_PAT, s)
+    ip = _unpackIP(ip)
     return IPV4Info(ip, port, keyinfo)
 
 class IPV4Info:
@@ -230,8 +249,8 @@
         self.keyinfo = keyinfo
 
     def pack(self):
-        assert len(keyinfo) == DIGEST_LEN
-        return struct.pack(IPV4_PAT, self.ip, self.port, keyinfo)
+        assert len(self.keyinfo) == DIGEST_LEN
+        return struct.pack(IPV4_PAT, _packIP(self.ip), self.port, self.keyinfo)
 
 def parseSMTPInfo(s):
     "XXXX"

Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- ServerInfo.py	29 May 2002 03:52:13 -0000	1.1
+++ ServerInfo.py	29 May 2002 22:51:58 -0000	1.2
@@ -18,5 +18,5 @@
     def getAddr(self): return self.addr
     def getPort(self): return self.port
     def getModulus(self): return self.modulus
-    return getKeyID(self): return self.keyid
+    def getKeyID(self): return self.keyid
     

Index: ServerProcess.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerProcess.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- ServerProcess.py	29 May 2002 17:46:23 -0000	1.2
+++ ServerProcess.py	29 May 2002 22:51:58 -0000	1.3
@@ -35,6 +35,8 @@
         if subh.major != 3 or subh.minor != 0:
             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")

Index: benchmark.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/benchmark.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- benchmark.py	29 May 2002 17:46:23 -0000	1.2
+++ benchmark.py	29 May 2002 22:51:58 -0000	1.3
@@ -57,6 +57,7 @@
     s2K = s1K*2
     s4K = s2K*2
     s8K = s4K*2
+    s28K = s1K*28
     s32K = s8K*4
 
     print "#==================== CRYPTO ======================="
@@ -64,6 +65,7 @@
 
     print "SHA1 (short)", timeit((lambda : sha1(short)), 100000)
     print "SHA1 (8K)", timeit((lambda : sha1(s8K)), 10000)
+    print "SHA1 (28K)", timeit((lambda : sha1(s28K)), 1000)
     print "SHA1 (32K)", timeit((lambda : sha1(s32K)), 1000)
 
     shakey = "8charstr"*2
@@ -71,8 +73,8 @@
     #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 (32K, unoptimized)", timeit(
-        (lambda : _ml.sha1("".join([shakey,s32K,shakey]))), 1000)
+    print "Lioness-keyed SHA1 (28K, unoptimized)", timeit(
+        (lambda : _ml.sha1("".join([shakey,s28K,shakey]))), 1000)
 
     print "TRNG (20 byte)", timeit((lambda: trng(20)), 100)
     print "TRNG (128 byte)", timeit((lambda: trng(128)), 100)
@@ -84,11 +86,13 @@
     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 (32K)", timeit((lambda: ctr_crypt(s32K,key)), 100)
 
     key = _ml.aes_key(key)
     print "aes (short,pre-key)", timeit((lambda: ctr_crypt(short,key)), 100000)
     print "aes (1K,pre-key)", timeit((lambda: ctr_crypt(s1K,key)), 10000)
+    print "aes (28K,pre-key)", timeit((lambda: ctr_crypt(s28K,key)), 100)
     print "aes (32K,pre-key)", timeit((lambda: ctr_crypt(s32K,key)), 100)
 
     print "aes (32K,pre-key,unoptimized)", timeit(
@@ -96,6 +100,7 @@
 
     print "prng (short)", timeit((lambda: prng(key,8)), 100000)
     print "prng (1K)", timeit((lambda: prng(key,1024)), 10000)
+    print "prng (28K)", timeit((lambda: prng(key,28678)), 100)
     print "prng (32K)", timeit((lambda: prng(key,32768)), 100)
     print "prng (32K, unoptimized)", timeit(
         (lambda: ctr_crypt('\x00'*32768, key)), 100)
@@ -104,10 +109,12 @@
     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 (4K)", timeit((lambda: lioness_encrypt(s4K, lkey)), 1000)
+    print "lioness E (28K)", timeit((lambda: lioness_encrypt(s28K, lkey)), 100)
     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)
     print "lioness D (4K)", timeit((lambda: lioness_decrypt(s4K, lkey)), 1000)
+    print "lioness D (28K)", timeit((lambda: lioness_decrypt(s28K, lkey)), 100)
     print "lioness D (32K)", timeit((lambda: lioness_decrypt(s32K, lkey)), 100)
 
     s70b = "10character"*7

Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -d -r1.3 -r1.4
--- test.py	29 May 2002 18:54:43 -0000	1.3
+++ test.py	29 May 2002 22:51:58 -0000	1.4
@@ -295,6 +295,28 @@
         self.assertEquals(s.getExtraBlocks(), extra)
 
         #XXXX Need failing tests, routinginfo tests.
+
+    def test_headers(self):
+        pass #XXXX
+
+    def test_message(self):
+        # 9 is relatively prime to all pwrs of 2.
+        m = ("abcdefghi"*(10000))[:32768]
+        msg = parseMessage(m)
+        self.assert_(msg.pack() == m)
+        self.assert_(msg.header1 == m[:2048])
+        self.assert_(msg.header2 == m[2048:4096])
+        self.assert_(msg.payload == m[4096:])
+        # FAILING CASES XXXX
+            
+    def test_ipv4info(self):
+        pass #XXXX
+
+    def test_smtpinfo(self):
+        pass #XXXX
+
+    def test_localinfo(self):
+        pass #XXXX
         
 #----------------------------------------------------------------------
 from mixminion.HashLog import HashLog
@@ -367,26 +389,160 @@
 
         h.close()
 
-    def test_headers(self):
-        pass #XXXX
 
-    def test_message(self):
-        pass #XXXX
+#----------------------------------------------------------------------
+import mixminion.BuildMessage
 
-    def test_ipv4info(self):
-        pass #XXXX
+class FakePRNG:
+    def getBytes(self,n):
+        return "\x00"*n
 
-    def test_smtpinfo(self):
-        pass #XXXX
+class BuildMessageTests(unittest.TestCase):
+    def setUp(self):
+        from ServerInfo import ServerInfo
+        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)
 
-    def test_localinfo(self):
-        pass #XXXX
+    def test_buildheader_1hop(self):
+        bhead = mixminion.BuildMessage._buildHeaders
+
+        head = bhead([self.server1], ["9"*16], 99, "Hi mom", AESCounterPRNG())
+        self.do_header_test(head,
+                            (self.pk1,),
+                            ["9"*16,],
+                            (99,),
+                            ("Hi mom",))
 
+    def test_buildheader_2hops(self):
+        bhead = mixminion.BuildMessage._buildHeaders
+        # 2 hops
+        head = bhead([self.server1, self.server2],
+                     ["9"*16, "1"*16], 99, "Hi mom", AESCounterPRNG()) 
+
+        ipv4 = mixminion.Formats.IPV4Info
+        self.do_header_test(head,
+                            (self.pk1, self.pk2),
+                            ["9"*16, "1"*16],
+                            (mixminion.Modules.FWD_TYPE, 99),
+                            (ipv4("127.0.0.2",3,"Z"*20).pack(),
+                             "Hi mom"))
+                            
+    def test_buildheader_3hops(self):
+        bhead = mixminion.BuildMessage._buildHeaders
+        # 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)
+        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):
+        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)
+            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:]))
+            ks = Keyset(secret)
+            key = ks.get(HEADER_SECRET_MODE)
+            prngkey = ks.get(RANDOM_JUNK_MODE)
+            head = ctr_crypt(head[128:]+prng(prngkey,128), key)
+
+    def test_extended_routinginfo(self):
+        #XXXX!!!! Code doesn't work 
+        pass
+
+    def test_constructmessage(self):
+        consMsg = mixminion.BuildMessage._constructMessage
+        
+        h1 = "abcdefgh"*(2048//8)
+        h2 = "aBcDeFgH"*(2048//8)
+
+        ######
+        ### non-reply case
+        secrets1 = [ x * 16 for x in "sqmsh"]
+        secrets2 = [ x * 16 for x in "osfrg"]
+        pld = """
+           Everyone has the right to freedom of opinion and expression; this
+           right includes freedom to hold opinions without interference and
+           to seek, receive and impart information and ideas through any
+           media and regardless of frontiers. 
+           """
+        pld += "\000"*(28*1024-len(pld))
+        
+        message = consMsg(secrets1, secrets2, h1, h2, pld)
+
+        self.assertEquals(len(message), mixminion.Formats.MESSAGE_LEN)
+        msg = mixminion.Formats.parseMessage(message)
+        head1, head2, payload = msg.header1, msg.header2, msg.payload
+        self.assert_(h1 == head1)
+
+        for path in secrets1, secrets2:
+            for s in path:
+                ks = Keyset(s)
+                hkey = ks.getLionessKeys(HEADER_ENCRYPT_MODE)
+                pkey = ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE)
+                if path is secrets1:
+                    head2 = lioness_decrypt(head2, hkey)
+                payload = lioness_decrypt(payload, pkey)
+
+            if path is secrets1:
+                swapkey = mixminion.Crypto.lioness_keys_from_payload(payload)
+                head2 = lioness_decrypt(head2, swapkey)
+
+        self.assert_(head2 == h2)
+        self.assert_(payload == pld)
+
+        #### Reply case
+        message = consMsg(secrets1, None, h1, h2, pld)
+        self.assertEquals(len(message), mixminion.Formats.MESSAGE_LEN)
+        msg = mixminion.Formats.parseMessage(message)
+        head1, head2, payload = msg.header1, msg.header2, msg.payload
+        self.assert_(h1 == head1)
+
+        for s in secrets1:
+            ks = Keyset(s)
+            hkey = ks.getLionessKeys(HEADER_ENCRYPT_MODE)
+            pkey = ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE)
+            head2 = lioness_decrypt(head2, hkey)
+            payload = lioness_decrypt(payload, pkey)
+        swapkey = mixminion.Crypto.lioness_keys_from_payload(payload)
+        head2 = lioness_decrypt(head2, swapkey)
+
+        self.assert_(head2 == h2)
+        self.assert_(payload == pld)
+        
+    def test_buildmessage(self):
+        #XXXX
+        pass
+
+    def test_buildreply(self):
+        #XXXX
+        pass
+
+    def test_buildstatelessreply(self):
+        #XXXX
+        pass
+            
 #----------------------------------------------------------------------
 import mixminion.ServerProcess
 #----------------------------------------------------------------------
-import mixminion.BuildMessage
-#----------------------------------------------------------------------
 
 def testSuite():
     suite = unittest.TestSuite()
@@ -396,6 +552,7 @@
     suite.addTest(tc(CryptoTests))
     suite.addTest(tc(FormatTests))
     suite.addTest(tc(HashLogTests))
+    suite.addTest(tc(BuildMessageTests))
     return suite
 
 def testAll():