[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[minion-cvs] More end-to-end testing
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.seul.org:/tmp/cvs-serv21986/lib/mixminion
Modified Files:
BuildMessage.py Crypto.py Packet.py test.py
Log Message:
More end-to-end testing
BuildMessage:
Move compression to BuildMessage.py
Debugging
More documentation
Crypto:
Remove extraneous import
Packet:
Fix thinko w.r.t. OAEP_OVERHEAD vs ENC_FWD_OVERHEAD on efwd messages
test:
More tests for end-to-end
Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -d -r1.14 -r1.15
--- BuildMessage.py 13 Oct 2002 01:34:44 -0000 1.14
+++ BuildMessage.py 14 Oct 2002 03:03:42 -0000 1.15
@@ -5,16 +5,17 @@
Code to construct messages and reply blocks."""
+import zlib
import operator
from mixminion.Packet import *
-from mixminion.Common import MixError
+from mixminion.Common import MixError, MixFatalError, getLog
import mixminion.Crypto as Crypto
import mixminion.Modules as Modules
__all__ = [ 'Address',
'buildForwardMessage', 'buildEncryptedMessage', 'buildReplyMessage',
'buildStatelessReplyBlock', 'buildReplyBlock', 'decodePayload',
- 'decodeForwardPayload', 'decodeEncryptedPayload',
+ 'decodeForwardPayload', 'decodeEncryptedForwardPayload',
'decodeReplyPayload', 'decodeStatelessReplyPayload' ]
def buildForwardMessage(payload, exitType, exitInfo, path1, path2,
@@ -44,21 +45,21 @@
if paddingPRNG is None: paddingPRNG = Crypto.AESCounterPRNG()
if secretRNG is None: secretRNG = paddingPRNG
- payload = _encodePayload(payload, OAEP_OVERHEAD, paddingPRNG)
+ payload = _encodePayload(payload, ENC_FWD_OVERHEAD, paddingPRNG)
sessionKey = secretRNG.getBytes(SECRET_LEN)
- payload = sessionKey+payLoad
+ payload = sessionKey+payload
rsaDataLen = key.get_modulus_bytes()-OAEP_OVERHEAD
rsaPart = payload[:rsaDataLen]
lionessPart = payload[rsaDataLen:]
# XXXX DOC
while 1:
- encrypted = mixminion.Crypto.pk_encrypt(rsaPart, key)
+ encrypted = Crypto.pk_encrypt(rsaPart, key)
if not (ord(encrypted[0]) & 0x80):
break
#XXXX doc mode 'End-to-end encrypt'
- k = mixminion.Crypto.Keyset(sessionKey).getLionessKeys("End-to-end encrypt")
- lionessPart = mixminion.Crypto.lioness_encrypt(lionessPart, k)
+ k = Crypto.Keyset(sessionKey).getLionessKeys("End-to-end encrypt")
+ lionessPart = Crypto.lioness_encrypt(lionessPart, k)
payload = encrypted + lionessPart
tag = payload[:TAG_LEN]
payload = payload[TAG_LEN:]
@@ -180,7 +181,7 @@
pass
if key is not None:
- p = decodeEncryptedPayload(payload, tag, key)
+ p = decodeEncryptedForwardPayload(payload, tag, key)
if p is not None:
return p
@@ -190,7 +191,7 @@
"XXXX"
return _decodePayload(payload)
-def decodeEncryptedPayload(payload, tag, key):
+def decodeEncryptedForwardPayload(payload, tag, key):
"XXXX"
msg = tag+payload
try:
@@ -220,10 +221,7 @@
prng = Crypto.AESCounterPRNG(seed)
secrets = [ prng.getBytes(SECRET_LEN) for _ in xrange(17) ]
- return decodeReplyBlock(payload, secrets, check=1)
-
-
-
+ return decodeReplyPayload(payload, secrets, check=1)
#----------------------------------------------------------------------
def _buildMessage(payload, exitType, exitInfo,
@@ -421,11 +419,19 @@
return Message(header1, header2, payload).pack()
+#----------------------------------------------------------------------
+# Payload-related helpers
+
def _encodePayload(payload, overhead, paddingPRNG):
- """XXXX
+ """Helper: compress a payload, pad it, and add extra fields (size and hash)
+ payload: the initial payload
+ overhead: number of bytes to omit from result
+ (0 or ENC_FWD_OVERHEAD)
+ paddingPRNG: generator for padding.
+
+ BUG: This should eventually support K-of-N.
"""
- # FFFF Do fragmentation here.
-
+ assert overhead in (0, ENC_FWD_OVERHEAD)
payload = compressData(payload)
length = len(payload)
@@ -438,7 +444,7 @@
return SingletonPayload(length, Crypto.sha1(payload), payload).pack()
def _getRandomTag(rng):
- "XXXX"
+ "Helper: Return a 20-byte string with the MSB of byte 0 set to 0."
b = ord(rng.getBytes(1)) & 0x7f
return chr(b) + rng.getBytes(TAG_LEN-1)
@@ -449,11 +455,63 @@
if not payload.isSingleton():
raise MixError("Message fragments not yet supported")
- #if payload.hash != Crypto.sha1(payload.data):
- # raise MixError("Hash doesn't match")
return uncompressData(payload.getContents())
def _checkPayload(payload):
+ 'Return true iff the hash on the given payload seems valid'
return payload[2:22] == Crypto.sha1(payload[22:])
+#----------------------------------------------------------------------
+# COMPRESSION FOR PAYLOADS
+
+_ZLIB_LIBRARY_OK = 0
+
+def compressData(payload):
+ """Given a string 'payload', compress it with zlib as specified in the
+ remailer spec."""
+ if not _ZLIB_LIBRARY_OK:
+ _validateZlib()
+ # 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.Z_DEFAULT_STRATEGY)
+ s1 = zobj.compress(payload)
+ s2 = zobj.flush()
+ return s1 + s2
+
+def uncompressData(payload):
+ """Uncompress a string 'payload'; raise ParseError if it is not valid
+ compressed data."""
+ try:
+ return zlib.decompress(payload)
+ 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,
+ because using a zlib version that compressed differently from zlib1.1.4
+ would make senders partitionable by payload compression.)
+ """
+ global _ZLIB_LIBRARY_OK
+ ver = getattr(zlib, "ZLIB_VERSION")
+ if ver and ver < "1.1.2":
+ raise MixFatalError("Zlib version %s is not supported"%ver)
+ if ver in ("1.1.2", "1.1.3", "1.1.4"):
+ _ZLIB_LIBRARY_OK = 1
+ return
+
+ getLog().warn("Unrecognized zlib version: %r. Spot-checking output", ver)
+ # 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'+\
+ '`\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'
+ if compressData("aZbAAcdefg"*1000) == good:
+ _ZLIB_LIBRARY_OK = 1
+ else:
+ _ZLIB_LIBRARY_OK = 0
+ raise MixFatalError("Zlib output not as exected.")
Index: Crypto.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Crypto.py,v
retrieving revision 1.21
retrieving revision 1.22
diff -u -d -r1.21 -r1.22
--- Crypto.py 13 Oct 2002 01:34:44 -0000 1.21
+++ Crypto.py 14 Oct 2002 03:03:42 -0000 1.22
@@ -12,7 +12,6 @@
import sys
import stat
import copy_reg
-import zlib
from types import StringType
import mixminion._minionlib as _ml
Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.11
retrieving revision 1.12
diff -u -d -r1.11 -r1.12
--- Packet.py 13 Oct 2002 01:34:44 -0000 1.11
+++ Packet.py 14 Oct 2002 03:03:42 -0000 1.12
@@ -14,10 +14,8 @@
'parseReplyBlock', 'ENC_SUBHEADER_LEN', 'HEADER_LEN',
'PAYLOAD_LEN', 'MAJOR_NO', 'MINOR_NO', 'SECRET_LEN', 'TAG_LEN',
'SINGLETON_PAYLOAD_OVERHEAD', 'OAEP_OVERHEAD',
- 'FRAGMENT_PAYLOAD_OVERHEAD',
- 'compressData', 'uncompressData']
+ 'FRAGMENT_PAYLOAD_OVERHEAD', 'ENC_FWD_OVERHEAD']
-import zlib
import struct
from socket import inet_ntoa, inet_aton
from mixminion.Common import MixError, floorDiv
@@ -243,13 +241,16 @@
FRAGMENT_MESSAGEID_LEN = 20
MAX_N_FRAGMENTS = 0x7ffff
+#XXXX DOCDOC
SINGLETON_PAYLOAD_OVERHEAD = 2 + DIGEST_LEN
FRAGMENT_PAYLOAD_OVERHEAD = 2 + DIGEST_LEN + FRAGMENT_MESSAGEID_LEN + 4
OAEP_OVERHEAD = 42
+#XXXX DOC DOC and e2e note is off by 4.
+ENC_FWD_OVERHEAD = OAEP_OVERHEAD - TAG_LEN + SECRET_LEN
def parsePayload(payload):
"XXXX"
- if len(payload) not in (PAYLOAD_LEN, PAYLOAD_LEN-OAEP_OVERHEAD):
+ if len(payload) not in (PAYLOAD_LEN, PAYLOAD_LEN-ENC_FWD_OVERHEAD):
raise ParseError("Payload has bad length")
bit0 = ord(payload[0]) & 0x80
if bit0:
@@ -298,7 +299,7 @@
assert 0 <= self.size <= len(self.data)
assert len(self.hash) == DIGEST_LEN
assert (PAYLOAD_LEN - SINGLETON_PAYLOAD_OVERHEAD - len(self.data)) in \
- (0, OAEP_OVERHEAD)
+ (0, ENC_FWD_OVERHEAD)
header = struct.pack(SINGLETON_UNPACK_PATTERN, self.size, self.hash)
return "%s%s" % (header, self.data)
@@ -322,30 +323,13 @@
assert len(self.msgID) == FRAGMENT_MESSAGEID_LEN
assert len(self.data) < self.msgLen < 0x100000000L
assert (PAYLOAD_LEN - FRAGMENT_PAYLOAD_OVERHEAD - len(self.data)) in \
- (0, OAEP_OVERHEAD)
+ (0, ENC_FWD_OVERHEAD)
idx = self.index | 0x8000
header = struct.pack(FRAGMENT_UNPACK_PATTERN, idx, self.hash,
self.msgID, self.msgLen)
return "%s%s" % (header, self.data)
#----------------------------------------------------------------------
-# COMPRESSION FOR PAYLOADS
-
-# FFFF Check for zlib acceptability. Check for correct parameters in zlib
-# FFFF module
-
-def compressData(payload):
- "XXXX"
- return zlib.compress(payload, 9)
-
-def uncompressData(payload):
- "XXXX"
- try:
- return zlib.decompress(payload)
- except zlib.error, _:
- raise ParseError("Error in compressed data")
-
-#----------------------------------------------------------------------
# REPLY BLOCKS
RB_UNPACK_PATTERN = "!4sBBL%dsHH%ss" % (HEADER_LEN, SECRET_LEN)
@@ -477,4 +461,3 @@
def pack(self):
"""Return the external representation of this routing info."""
return self.tag + self.user
-
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.29
retrieving revision 1.30
diff -u -d -r1.29 -r1.30
--- test.py 13 Oct 2002 01:34:44 -0000 1.29
+++ test.py 14 Oct 2002 03:03:42 -0000 1.30
@@ -202,7 +202,7 @@
_ml.openssl_seed("")
def test_oaep(self):
- import Crypto
+ import mixminion.Crypto as Crypto
_add = Crypto._add_oaep_padding
_check = Crypto._check_oaep_padding
for add,check in ((_ml.add_oaep_padding, _ml.check_oaep_padding),
@@ -647,7 +647,7 @@
contents = ("payload"*(4*1024))[:28*1024 - 22]
hash = "HASH"*5
singleton_payload_1 = "\x00\xff"+hash+contents
- singleton_payload_2 = singleton_payload_1[:-42] #OAEP overhead
+ singleton_payload_2 = singleton_payload_1[:-38] #efwd overhead
p1 = parsePayload(singleton_payload_1)
p2 = parsePayload(singleton_payload_2)
self.failUnless(p1.isSingleton() and p2.isSingleton())
@@ -656,7 +656,7 @@
self.assertEquals(p1.hash,hash)
self.assertEquals(p2.hash,hash)
self.assertEquals(p1.data,contents)
- self.assertEquals(p2.data,contents[:-42])
+ self.assertEquals(p2.data,contents[:-38])
self.assertEquals(p1.getContents(), contents[:255])
self.assertEquals(p2.getContents(), contents[:255])
self.assertEquals(p1.pack(),singleton_payload_1)
@@ -665,7 +665,7 @@
self.assertEquals(singleton_payload_1,
SingletonPayload(255, hash, contents).pack())
self.assertEquals(singleton_payload_2,
- SingletonPayload(255, hash, contents[:-42]).pack())
+ SingletonPayload(255, hash, contents[:-38]).pack())
# Impossible payload lengths
self.failUnlessRaises(ParseError,parsePayload,singleton_payload_1+"a")
@@ -680,7 +680,7 @@
assert len(msgID) == 20
contents = contents[:28*1024 - 46]
frag_payload_1 = "\x80\x02"+hash+msgID+"\x00\x01\x00\x00"+contents
- frag_payload_2 = frag_payload_1[:-42] # oaep overhead
+ frag_payload_2 = frag_payload_1[:-38] # efwd overhead
p1 = parsePayload(frag_payload_1)
p2 = parsePayload(frag_payload_2)
self.failUnless(not p1.isSingleton() and not p2.isSingleton())
@@ -693,14 +693,14 @@
self.assertEquals(p1.msgLen,64*1024)
self.assertEquals(p2.msgLen,64*1024)
self.assertEquals(p1.data,contents)
- self.assertEquals(p2.data,contents[:-42])
+ self.assertEquals(p2.data,contents[:-38])
self.assertEquals(p1.pack(),frag_payload_1)
self.assertEquals(p2.pack(),frag_payload_2)
self.assertEquals(frag_payload_1,
FragmentPayload(2,hash,msgID,64*1024,contents).pack())
self.assertEquals(frag_payload_2,
- FragmentPayload(2,hash,msgID,64*1024,contents[:-42]).pack())
+ FragmentPayload(2,hash,msgID,64*1024,contents[:-38]).pack())
# Impossible payload lengths
self.failUnlessRaises(ParseError,parsePayload,frag_payload_1+"a")
@@ -710,9 +710,9 @@
# Impossible message sizes
min_payload_1 = "\x80\x02"+hash+msgID+"\x00\x00\x6F\xD3"+contents
bad_payload_1 = "\x80\x02"+hash+msgID+"\x00\x00\x6F\xD2"+contents
- min_payload_2 = "\x80\x02"+hash+msgID+"\x00\x00\x6F\xA9"+contents[:-42]
- bad_payload_2 = "\x80\x02"+hash+msgID+"\x00\x00\x6F\xA8"+contents[:-42]
- min_payload_3 = "\x80\x02"+hash+msgID+"\x00\x00\x6F\xD2"+contents[:-42]
+ min_payload_2 = "\x80\x02"+hash+msgID+"\x00\x00\x6F\xAD"+contents[:-38]
+ bad_payload_2 = "\x80\x02"+hash+msgID+"\x00\x00\x6F\xAC"+contents[:-38]
+ min_payload_3 = "\x80\x02"+hash+msgID+"\x00\x00\x6F\xD2"+contents[:-38]
parsePayload(min_payload_1)
parsePayload(min_payload_2)
parsePayload(min_payload_3)
@@ -793,7 +793,6 @@
pk1 = pk_generate()
pk2 = pk_generate()
pk3 = pk_generate()
-
class FakeServerInfo:
"""Represents a Mixminion server, and the information needed to send
@@ -823,6 +822,51 @@
self.server2 = FakeServerInfo("127.0.0.2", 3, self.pk2, "Z"*20)
self.server3 = FakeServerInfo("127.0.0.3", 5, self.pk3, "Q"*20)
+ def test_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):
+ c = BuildMessage.compressData(m)
+ self.assertEquals(m, BuildMessage.uncompressData(c))
+
+ self.failUnlessRaises(ParseError, BuildMessage.uncompressData, "3")
+
+ for _ in xrange(20):
+ for _ in xrange(20):
+ m = p.getBytes(p.getInt(1000))
+ try:
+ BuildMessage.uncompressData(m)
+ except ParseError, _:
+ pass
+ #FFFF Find a decent test vector.
+
+ def test_payload_helpers(self):
+ "test helpers for payload encoding"
+ p = AESCounterPRNG()
+ for i in xrange(10):
+ t = BuildMessage._getRandomTag(p)
+ self.assertEquals(20, len(t))
+ self.assertEquals(0, ord(t[0])&0x80)
+
+ b = p.getBytes(28*1024)
+ self.assert_(not BuildMessage._checkPayload(b))
+
+ for m in (p.getBytes(3000), p.getBytes(10000), "", "q", "blznerty"):
+ for ov in 0, 42-20+16: # encrypted forward overhead
+ pld = BuildMessage._encodePayload(m,ov,p)
+ self.assertEquals(28*1024, len(pld)+ov)
+ comp = BuildMessage.compressData(m)
+ self.assert_(pld[22:].startswith(comp))
+ self.assertEquals(sha1(pld[22:]),pld[2:22])
+ self.assertEquals(len(comp), ord(pld[0])*256+ord(pld[1]))
+ self.assertEquals(0, ord(pld[0])&0x80)
+ self.assertEquals(m, BuildMessage._decodePayload(pld))
+
+ # XXXX Test failing cases
+
def test_buildheader_1hop(self):
bhead = BuildMessage._buildHeader
@@ -884,6 +928,7 @@
subh = mixminion.Packet.parseSubheader(pk_decrypt(head[:128], pk))
if secret:
self.assertEquals(subh.secret, secret)
+ retsecrets.append(secret)
else:
secret = subh.secret
retsecrets.append(secret)
@@ -901,7 +946,7 @@
else:
ext = 20
if ri:
- tag = ri[:20]
+ tag = subh.routinginfo[:20]
if not subh.isExtended():
if ri:
self.assertEquals(subh.routinginfo[ext:], ri)
@@ -1079,6 +1124,7 @@
def test_build_fwd_message(self):
bfm = BuildMessage.buildForwardMessage
+ befm = BuildMessage.buildEncryptedForwardMessage
payload = "Hello"
m = bfm(payload, 500, "Goodbye",
@@ -1109,6 +1155,28 @@
("Goodbye",) ),
"Hello")
+ # Encrypted forward message
+ rsa1, rsa2 = pk_generate(1024), pk_generate(2048)
+ for rsakey in rsa1,rsa2:
+ m = befm(payload, 500, "Phello",
+ [self.server1, self.server2],
+ [self.server3, self.server2],
+ rsakey)
+ def decoder(p,t,key=rsakey):
+ return BuildMessage.decodeEncryptedForwardPayload(p,t,key)
+
+ 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, 500),
+ (self.server2.getRoutingInfo().pack(),
+ "Phello") ),
+ payload,
+ decoder=decoder)
+
def test_buildreply(self):
brb = BuildMessage.buildReplyBlock
bsrb = BuildMessage.buildStatelessReplyBlock
@@ -1135,35 +1203,53 @@
[self.server3, self.server1],
reply)
- #XXXX Explain this thing
def decoder(p,t,secrets=secrets):
- return BuildMessage.decodeReplyPayload(p,secrets[-1:])
+ return BuildMessage.decodeReplyPayload(p,secrets)
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, hsecrets, # stop after first pk???????XXXX
+ (pks_1, hsecrets,
(FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,SMTP_TYPE),
infos+("no-such-user@invalid",)),
"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", "Galaxy Far Away", 0)
sec,(loc,) = self.do_header_test(reply.header, pks_1, None,
(FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,MBOX_TYPE),
infos+(None,))
- if 0: #XXXXX
- 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 _ in range(5) ])
+
+ self.assertEquals(loc[20:], "fred")
+
+ seed = loc[:20]
+ prng = AESCounterPRNG(sha1(seed+"Galaxy Far AwayGenerate")[:16])
+ sec.reverse()
+ self.assertEquals(sec, [ prng.getBytes(16) for _ in range(len(sec)) ])
+
+ m = brm("Information? What's the world come to?",
+ [self.server3, self.server1],
+ reply)
+
+ def decoder2(p,t):
+ return BuildMessage.decodeStatelessReplyPayload(p,t,
+ "Galaxy Far Away")
+ 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, None,
+ (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,MBOX_TYPE),
+ infos+("fred",)),
+ "Information? What's the world come to?",
+ decoder=decoder2)
+
#----------------------------------------------------------------------
# Having tested BuildMessage without using PacketHandler, we can now use
@@ -1221,7 +1307,7 @@
if decoder is None:
p = BuildMessage.decodeForwardPayload(p)
else:
- p = decoder(p, res[1][1])
+ p = decoder(p, res[1][1][:20])
self.assert_(p.startswith(payload))
break
@@ -2627,6 +2713,10 @@
suite = unittest.TestSuite()
loader = unittest.TestLoader()
tc = loader.loadTestsFromTestCase
+
+ if 0:
+ suite.addTest(tc(BuildMessageTests))
+ return suite
suite.addTest(tc(ClientMainTests))
suite.addTest(tc(ServerMainTests))