[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[minion-cvs] Backport; tests; hacking: Core code is clean, commented...
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.seul.org:/tmp/cvs-serv23515/lib/mixminion
Modified Files:
BuildMessage.py Common.py Crypto.py HashLog.py Modules.py
ServerInfo.py __init__.py benchmark.py test.py
Added Files:
.cvsignore Packet.py PacketHandler.py
Removed Files:
Formats.py ServerProcess.py
Log Message:
Backport; tests; hacking: Core code is clean, commented, documented, and tested
- Backported to older versions of Python. We now support 2.0 through
2.2 inclusive. This included:
- Removing uses of // operator.
- Checking version properly in setup.py
- Removing use of nested scopes.
- Replace type(foo) == str with type(foo) == type("")
- Bundling Python's unittest.py module so we can run tests on systems
(Python 2.0) that don't include it.
- In crypt.c, we can no longer go from openssl BIGNUMs to Python Longs
using _PyLong_(To|From)ByteArray -- the latter method didn't exist
until 2.2. We have to to/from a hex representation instead.
- Better message on insane-endianness case.
- Rename 'Formats' module to 'Packet'.
- Rename 'ServerProcess' module to 'PacketHandler'.
- Document and comment all remaining code.
- Changes to Packet: (Formerly Formats)
- Add ReplyBlock object
- Fix bugs in header slicing
- Freak out on overlong subheaders.
- Changes to BuildMessage:
- Make BuildStatelessReplyBlock a bit more paranoid about missing userKey.
- Rejoin _buildFoo and _buildFoo_impl
- Grouse early if header will be too long.
- Misc cleanups
- New tests: Long intermediate info, fail on overlong routing info
- Changes to Crypto:
- Use symbolic constants throughout
- Add cache for TRNG
- Changes to Hashlog:
- Make 'sync' method conditional on underlying sync: this allows us
to support dumbdbm.
- Changes to PacketHandler: (formerly ServerProcess)
- Remove processMessage intermediary; rename _processMessage.
- Add support for multiple keys
- Make setup more sane
- Add note on timing attacks
- New tests: Most failing cases
- Changes to benchMark:
- Restructure handling of primitives, loop overhead
- Make cleanup process handle dumbdbm.
- Add actual vs ideal comparisons for Lioness and PacketHandler
- Changes to crypt.c:
- Write more idiomatic and paranoid C.
- Make failing SHA cases fail faster
- Backport to py2.0->py2.2; see above
--- NEW FILE: .cvsignore ---
_unittest.py
--- NEW FILE: Packet.py ---
# Copyright 2002 Nick Mathewson. See LICENSE for licensing information.
# $Id: Packet.py,v 1.1 2002/06/02 06:11:16 nickm Exp $
"""mixminion.Packet
Functions, classes, and constants to parse and unparse Mixminion
messages and related structures."""
__all__ = [ 'ParseError', 'Message', 'Header', 'Subheader',
'parseMessage', 'parseHeader', 'parseSubheader',
'getTotalBlocksForRoutingInfoLen', 'ReplyBlock',
'IPV4Info', 'SMTPInfo', 'LocalInfo', 'parseIPV4Info',
'parseSMTPInfo', 'parseLocalInfo', 'ENC_SUBHEADER_LEN',
'HEADER_LEN', 'PAYLOAD_LEN', 'MAJOR_NO', 'MINOR_NO',
'SECRET_LEN']
import types, struct
import mixminion.Common
# Major and minor number for the understood packet format.
# ???? The spec needs to specify this.
MAJOR_NO, MINOR_NO = 0,1
# Length of a Mixminion message
MESSAGE_LEN = 1 << 15
# Length of a header section
HEADER_LEN = 128 * 16
# Length of a single payload
PAYLOAD_LEN = MESSAGE_LEN - HEADER_LEN*2
# Smallest possible size for a subheader
MIN_SUBHEADER_LEN = 42
# Most information we can fit into a subheader
MAX_SUBHEADER_LEN = 86
# Longest routing info that will fit in the main subheader
MAX_ROUTING_INFO_LEN = MAX_SUBHEADER_LEN - MIN_SUBHEADER_LEN
# Length of a subheader, once RSA-encoded.
ENC_SUBHEADER_LEN = 128
# Length of a digest
DIGEST_LEN = 20
# Length of a secret key
SECRET_LEN = 16
# Most info that fits in a single extened subheader
ROUTING_INFO_PER_EXTENDED_SUBHEADER = ENC_SUBHEADER_LEN
class ParseError(mixminion.Common.MixError):
"""Thrown when a message or portion thereof is incorrectly formatted."""
pass
def parseMessage(s):
"""parseMessage(s) -> Message
Given a 32K string, returns a Message object that breaks it into
two headers and a payload."""
if len(s) != MESSAGE_LEN:
raise ParseError("Bad message length")
return Message(s[:HEADER_LEN],
s[HEADER_LEN:HEADER_LEN*2],
s[HEADER_LEN*2:])
class Message:
"""Represents a complete Mixminion packet
Fields: header1, header2, payload"""
def __init__(self, header1, header2, payload):
"""Message(header1, header2, payload) -> msg
Creates a new Message object from three strings."""
self.header1 = header1
self.header2 = header2
self.payload = payload
def pack(self):
"""Returns the 32K string value of this message."""
return "".join([self.header1,self.header2,self.payload])
def parseHeader(s):
"""parseHeader(s) -> Header
Converts a 2K string into a Header object"""
if len(s) != HEADER_LEN:
raise ParseError("Bad header length")
return Header(s)
class Header:
"""Represents a 2K Mixminion header"""
def __init__(self, contents):
self.contents = contents
def __getitem__(self, i):
"""header[i] -> str
Returns the i'th encoded subheader of this header, for i in 0..15"""
if i < 0: i = 16+i
return self.contents[i*ENC_SUBHEADER_LEN:
(i+1)*ENC_SUBHEADER_LEN]
def __getslice__(self, i, j):
"""header[i] -> str
Returns a slice of the i-j'th subheaders of this header."""
if j > 16: j = 16
if i < 0: i=16+i
if j < 0: j=16+j
return self.contents[i*ENC_SUBHEADER_LEN:
j*ENC_SUBHEADER_LEN]
def __len__(self):
return 16
# A subheader begins with: a major byte, a minor byte, SECRET_LEN secret
# bytes, DIGEST_LEN digest bytes, a routing_len short, and a routing_type
# short.
SH_UNPACK_PATTERN = "!BB%ds%dsHH" % (SECRET_LEN, DIGEST_LEN)
def parseSubheader(s):
"""parseSubheader(s) -> Subheader
Converts a decoded Mixminion subheader into a Subheader object"""
if len(s) < MIN_SUBHEADER_LEN:
raise ParseError("Header too short")
if len(s) > MAX_SUBHEADER_LEN:
raise ParseError("Header too long")
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]
return Subheader(major,minor,secret,digest,rt,ri,rlen)
def getTotalBlocksForRoutingInfoLen(bytes):
"""Returns the number of subheraders that will be needed for a hop
whose routinginfo is (bytes) long."""
if bytes <= MAX_ROUTING_INFO_LEN:
return 1
else:
extraBytes = bytes - MAX_ROUTING_INFO_LEN
return 2 + divmod(extraBytes,ROUTING_INFO_PER_EXTENDED_SUBHEADER)[0]
class Subheader:
"""Represents a decoded Mixminion header
Fields: major, minor, secret, digest, routinglen, routinginfo,
routingtype.
A Subheader can exist in a half-initialized state where routing
info has been read from the first header, but not from the
extened headers. If this is so, routinglen will be > len(routinginfo).
"""
def __init__(self, major, minor, secret, digest, routingtype,
routinginfo, routinglen=None):
self.major = major
self.minor = minor
self.secret = secret
self.digest = digest
if routinglen == None:
self.routinglen = len(routinginfo)
else:
self.routinglen = routinglen
self.routingtype = routingtype
self.routinginfo = routinginfo
def __repr__(self):
return ("Subheader(major=%(major)r, minor=%(minor)r, "+
"secret=%(secret)r, digest=%(digest)r, "+
"routingtype=%(routingtype)r, routinginfo=%(routinginfo)r, "+
"routinglen=%(routinglen)r)")% self.__dict__
def setRoutingInfo(self, info):
"""Changes the routinginfo, and the routinglength to correspond."""
self.routinginfo = info
self.routinglen = len(info)
def isExtended(self):
"""Returns true iff the routinginfo is too long to fit in a single
subheader."""
return self.routinglen > MAX_ROUTING_INFO_LEN
def getNExtraBlocks(self):
"""Returns the number of extra blocks that will be needed to fit
the routinginfo."""
return getTotalBlocksForRoutingInfoLen(self.routinglen)-1
def appendExtraBlocks(self, data):
"""appendExtraBlocks(str)
Given additional (decoded) blocks of routing info, adds them
to the routinginfo of this object."""
nBlocks = self.getNExtraBlocks()
assert len(data) == nBlocks * ENC_SUBHEADER_LEN
raw = [self.routinginfo]
for i in range(nBlocks):
block = data[i*ENC_SUBHEADER_LEN:(i+1)*ENC_SUBHEADER_LEN]
raw.append(block)
self.routinginfo = ("".join(raw))[:self.routinglen]
def pack(self):
"""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
info = self.routinginfo[:MAX_ROUTING_INFO_LEN]
return struct.pack(SH_UNPACK_PATTERN,
self.major,self.minor,self.secret,self.digest,
self.routinglen, self.routingtype)+info
def getExtraBlocks(self):
"""getExtraBlocks() -> [ str, ...]
Returns a list of (unencrypted) blocks of extra routing info."""
if not self.isExtended():
return []
else:
info = self.routinginfo[MAX_ROUTING_INFO_LEN:]
result = []
for i in range(self.getNExtraBlocks()):
content = info[i*ROUTING_INFO_PER_EXTENDED_SUBHEADER:
(i+1)*ROUTING_INFO_PER_EXTENDED_SUBHEADER]
missing = ROUTING_INFO_PER_EXTENDED_SUBHEADER-len(content)
if missing > 0:
content += '\000'*missing
result.append(content)
return result
#FFFF We don't have an interchange format for these yet, so there's no way
#FFFF to parse or unparse 'em.
class ReplyBlock:
"""A mixminion reply block, including the address of the first hop
on the path, and a ServerInfo for the server."""
def __init__(self, header, addr):
self.header = header
self.addr = addr
def _packIP(s):
"""Helper method: converts a dotted-decimal IPv4 address into a
four-byte encoding. Raises ParseError if the input is not a
dotted quad of 0..255"""
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):
"""Helper method: convers a four-byte IPv4 address into a dotted quad."""
if len(s) != 4: raise ParseError("Malformed IP")
return ".".join(map(str, struct.unpack("!BBBB", s)))
# An IPV4 address (Used by SWAP_FWD and FWD) is packed as: four bytes
# of IP address, a short for the portnum, and DIGEST_LEN bytes of keyid.
IPV4_PAT = "!4sH%ds" % DIGEST_LEN
def parseIPV4Info(s):
"""parseIP4VInfo(s) -> IPV4Info
Converts routing info for an IPV4 address into an IPV4Info object,
suitable for use by FWD or SWAP_FWD modules."""
if len(s) != 4+2+DIGEST_LEN:
raise ParseError("IPV4 information with wrong length")
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)
class IPV4Info:
"""An IPV4Info object represents the routinginfo for a FWD or SWAP_FWD hop.
Fields: ip (a dotted quad), port (an int from 0..65535), and keyinfo
(a digest)."""
def __init__(self, ip, port, keyinfo):
assert 0<=port<=65535
self.ip = ip
self.port = port
self.keyinfo = keyinfo
def pack(self):
assert len(self.keyinfo) == DIGEST_LEN
return struct.pack(IPV4_PAT, _packIP(self.ip), self.port, self.keyinfo)
def parseSMTPInfo(s):
"""Converts the encoding of an SMTP routinginfo into an SMTPInfo object."""
lst = s.split("\000",1)
if len(lst) == 1:
return SMTPInfo(s,None)
else:
return SMTPInfo(lst[0], lst[1])
class SMTPInfo:
"""Represents the routinginfo for an SMTP hop.
Fields: email (an email address), tag (an arbitrary tag, optional)."""
def __init__(self, email, tag):
self.email = email
self.tag = tag
def pack(self):
if self.tag != None:
return self.email+"\000"+self.tag
else:
return self.email
def parseLocalInfo(s):
"""Converts the encoding of an LOCAL routinginfo into an LocalInfo
object."""
lst = s.split("\000",1)
if len(lst) == 1:
return LocalInfo(s,None)
else:
return LocalInfo(lst[0], lst[1])
class LocalInfo:
"""Represents the routinginfo for a LOCAL hop.
Fields: user (a user identifier), tag (an arbitrary tag, optional)."""
def __init__(self, user, tag):
self.user = user
assert user.find('\000') == -1
self.tag = tag
def pack(self):
if self.tag:
return self.user+"\000"+self.tag
else:
return self.user
--- NEW FILE: PacketHandler.py ---
# Copyright 2002 Nick Mathewson. See LICENSE for licensing information.
# $Id: PacketHandler.py,v 1.1 2002/06/02 06:11:16 nickm Exp $
"""mixminion.PacketHandler: Code to process mixminion packets"""
import mixminion.Crypto as Crypto
import mixminion.Packet as Packet
from mixminion.Packet import MAJOR_NO, MINOR_NO
import mixminion.Modules as Modules
import mixminion.Common as Common
__all__ = [ 'PacketHandler', 'ContentError' ]
class ContentError(Common.MixError):
"""Exception raised when a packed is malformatted or unacceptable."""
pass
class PacketHandler:
"""Class to handle processing packets. Given an incoming packet,
it removes one layer of encryption, does all necessary integrity
checks, swaps headers if necessary, re-pads, and decides whether
to drop the message, relay the message, or send the message to
an exist handler."""
def __init__(self, privatekey, hashlog):
"""PacketHandler(privatekey, hashlog)
Constructs a new packet handler, given a private key object for
header encryption, and a hashlog object to prevent replays.
A sequence of private keys may be provided, if you'd like the
server to accept messages encrypted with any of them. Beware,
though: this slows down the packet handler a lot.
"""
# ???? Any way to support multiple keys in protocol?
if type(privatekey) in (type(()), type([])):
self.privatekey = privatekey
else:
self.privatekey = (privatekey, )
self.hashlog = hashlog
# Raises ParseError, ContentError, SSLError.
# Returns oneof (None), (EXIT, argl), ("QUEUE", (ipv4info, msg))
def processMessage(self, msg):
"""ph.processMessage(msg)
Given a 32K mixminion message, processes it completely.
Returns one of:
None [if the mesesage should be dropped.
("EXIT",
(routing_type, routing_info, application_key,
payload)) [if this is the exit node]
("QUEUE", (ipv4info, message_out))
[if this is a forwarding node]
May raise SSLError, ParseError, or ContentError if the packet
is malformatted, misencrypted, unparseable, repeated, or otherwise
unhandleable.
WARNING: This implementation does nothing to prevent timing
attacks: dropped messages, messages with digests, replayed
messages, and exit messages are all processed faster than
forwarded messages. You must prevent timing attacks elsewhere."""
# Break into headers and payload
msg = Packet.parseMessage(msg)
header1 = Packet.parseHeader(msg.header1)
# Try to decrypt the first subheader.
enc_subh = header1[0]
subh = None
err = None
for pk in self.privatekey:
try:
subh = Crypto.pk_decrypt(enc_subh, pk)
except Crypto.SSLError, e:
err = e
if not subh:
raise err
subh = Packet.parseSubheader(subh)
# Check the version: can we read it?
if subh.major != MAJOR_NO or subh.minor != MINOR_NO:
raise ContentError("Invalid protocol version")
# Check the digest: is it correct?
digest = Crypto.sha1(header1[1:])
if digest != subh.digest:
raise ContentError("Invalid digest")
# Get ready to generate message keys.
keys = Crypto.Keyset(subh.secret)
# Replay prevention
replayhash = keys.get(Crypto.REPLAY_PREVENTION_MODE, 20)
if self.hashlog.seenHash(replayhash):
raise ContentError("Duplicate message detected.")
else:
self.hashlog.logHash(replayhash)
# If we're meant to drop, drop now.
rt = subh.routingtype
if rt == Modules.DROP_TYPE:
return None
# Prepare the key to decrypt the header in counter mode. We'll be
# using this more than once.
header_sec_key = Crypto.aes_key(keys.get(Crypto.HEADER_SECRET_MODE))
# If the subheader says that we have extra blocks of routing info,
# decrypt and parse them now.
if subh.isExtended():
nExtra = subh.getNExtraBlocks()
if (rt < Modules.MIN_EXIT_TYPE) or (nExtra > 15):
# None of the native methods allow multiple blocks; no
# size can be longer than the number of bytes in the rest
# of the header.
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:]
# Decrypt the payload.
payload = Crypto.lioness_decrypt(msg.payload,
keys.getLionessKeys(Crypto.PAYLOAD_ENCRYPT_MODE))
# If we're an exit node, there's no need to process the headers
# further.
if rt >= Modules.MIN_EXIT_TYPE:
return ("EXIT",
(rt, subh.routinginfo,
keys.get(Crypto.APPLICATION_KEY_MODE),
payload))
# If we're not an exit node, make sure that what we recognize our
# routing type.
if rt not in (Modules.SWAP_FWD_TYPE, Modules.FWD_TYPE):
raise ContentError("Unrecognized Mixminion routing type")
# Pad the rest of header 1
remainingHeader = remainingHeader +\
Crypto.prng(keys.get(Crypto.PRNG_MODE),
Packet.HEADER_LEN-len(remainingHeader))
# Decrypt the rest of header 1, encrypting the padding.
header1 = Crypto.ctr_crypt(remainingHeader, header_sec_key, nExtra*128)
# Decrypt header 2.
header2 = Crypto.lioness_decrypt(msg.header2,
keys.getLionessKeys(Crypto.HEADER_ENCRYPT_MODE))
# If we're the swap node, decrypt header2 with a hash of the
# payload, and swap the headers.
if rt == Modules.SWAP_FWD_TYPE:
hkey = Crypto.lioness_keys_from_payload(payload)
header2 = Crypto.lioness_decrypt(header2, hkey)
header1, header2 = header2, header1
# Build the address object for the next hop
address = Packet.parseIPV4Info(subh.routinginfo)
# Construct the message for the next hop.
msg = Packet.Message(header1, header2, payload).pack()
return ("QUEUE", (address, msg))
Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- BuildMessage.py 31 May 2002 12:47:58 -0000 1.4
+++ BuildMessage.py 2 Jun 2002 06:11:16 -0000 1.5
@@ -1,7 +1,12 @@
# Copyright 2002 Nick Mathewson. See LICENSE for licensing information.
# $Id$
-from mixminion.Formats import *
+"""mixminion.BuildMessage
+
+ Code to construct messages and reply blocks."""
+
+from mixminion.Packet import *
+from mixminion.Common import MixError
import mixminion.Crypto as Crypto
import mixminion.Modules as Modules
import operator
@@ -10,38 +15,78 @@
'buildStatelessReplyBlock' ]
def buildForwardMessage(payload, exitType, exitInfo, path1, path2):
- "XXXX"
+ """buildForwardMessage(payload, exitType, exitInfo, path1, path2) ->str
+
+ Constructs a forward message.
+ payload: The payload to deliver.
+ exitType: The routing type for the final node
+ exitType: The routing info for the final node
+ path1: Sequence of ServerInfo objects for the first leg of the path
+ path1: Sequence of ServerInfo objects for the 2nd leg of the path
+ """
return _buildMessage(payload, exitType, exitInfo, path1, path2)
def buildReplyMessage(payload, path1, replyBlock):
- # Bad iface; shouldn't take a tuple
- "XXXX"
+ """buildReplyMessage(payload, path1, replyBlock) ->str
+
+ Builds a message using a reply block. 'path1' is a sequence of
+ ServerInfo for the nodes on the first leg of the path."""
return _buildMessage(payload, None, None,
path1=path1,
reply=replyBlock)
-# Bad interface: this shouldn't return a tuple.
def buildReplyBlock(path, exitType, exitInfo, secretPRNG=None):
- "XXXX"
+ """buildReplyBlock(path, exitType, exitInfo, secretPRNG=None)
+ -> (Reply block, secret list)
+
+ Returns a newly-constructed reply block and a list of secrets used
+ to make it.
+
+ path: A list of ServerInfo
+ exitType: Routing type to use for the final node
+ exitInfo: Routing info for the final node
+ secretPRNG: A PRNG to use for generating secrets. If not
+ provided, uses an AES counter-mode stream seeded from our
+ entropy source.
+ """
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
+ return ReplyBlock(header, path[0]), secrets
+
+# Maybe we shouldn't even allow this to be called with userKey==None.
+def buildStatelessReplyBlock(path, user, userKey, email=0):
+ """buildStatelessReplyBlock(path, user, userKey, email=0) -> ReplyBlock
+
+ Constructs a 'stateless' reply block that does not require the
+ reply-message recipient to remember a list of secrets.
+ Instead, all secrets are generated from an AES counter-mode
+ stream, and the seed for the stream is stored in the 'tag'
+ field of the final block's routing info.
+
+ If the user provides a 'userkey', that key is used to encrypt
+ the seed before storing it in the tag field. Otherwise, the
+ seed is stored in the clear. USERS SHOULD ALWAYS SET 'userkey'
+ IF THE EXIT INFORMATION WILL BE TRAVELING OVER THE NETWORK, OR
+ IF THEY DO NOT PERSONALLY CONTROL THE EXIT NODE. Otherwise,
+ their anonymity can be completely broken.
+
+ path: a list of ServerInfo objects
+ user: the users username/email address
+ userKey: an AES key to encrypt the seed, or None.
+ email: If true, delivers via SMTP; else delivers via LOCAL.
+ """
+ if email and userKey:
+ raise MixError("Requested EMail delivery without password-protection")
-# Bad interface: userkey should only be None if we trust the final node
-# a lot.
-def buildStatelessReplyBlock(path, user, userKey=None, email=0):
- "XXXX"
- # COMMENT
- if email:
- assert userKey
seed = Crypto.trng(16)
if userKey:
tag = Crypto.ctr_crypt(seed,userKey)
else:
tag = seed
+
if email:
exitType = Modules.SMTP_TYPE
exitInfo = SMTPInfo(user, "RTRN"+tag).pack()
@@ -53,87 +98,106 @@
return buildReplyBlock(path, exitType, exitInfo, prng)[0]
#----------------------------------------------------------------------
-# needs a more informative name
def _buildMessage(payload, exitType, exitInfo,
path1, path2=None, reply=None, paddingPRNG=None, paranoia=0):
- "XXXX"
+ """_buildMessage(payload, exitType, exitInfo, path1, path2=None,
+ reply=None, paddingPRNG=None, paranoia=0) -> str
+
+ Helper method to create a message.
+
+ The following fields must be set:
+ payload: the intended exit payload.
+ (exitType, exitInfo): the routing type and info for the final node.
+ (Ignored for reply messages)
+ path1: a sequence of ServerInfo objects, one for each of the nodes
+ on the first leg of the path.
+
+ The following fields must be set for a forward message:
+ path2: a sequence of ServerInfo objects, one for each of the nodes
+ on the second leg of the path.
+
+ The following fields must be set for a reply message:
+ reply: a ReplyBlock object
+
+ The following fields are optional:
+ paddingPRNG: A pseudo-random number generator used to pad the headers
+ and the payload. If not provided, we use a counter-mode AES stream
+ seeded from our entropy source.
+ paranoia: If this is false, we use the padding PRNG to generate
+ header secrets too. Otherwise, we read all of our header secrets
+ from the true entropy source.
+ """
assert path2 or reply
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 += paddingPRNG.getBytes(PAYLOAD_LEN-len(payload))
-
- nHops = len(path1)
- if path2: nHops += len(path2)
+ ### SETUP CODE: let's handle all the variant cases.
+ # Set up the random number generators.
+ if paddingPRNG == None:
+ paddingPRNG = Crypto.AESCounterPRNG()
if paranoia:
+ nHops = len(path1)
+ if path2: nHops += len(path2)
secretRNG = Crypto.TrueRNG(SECRET_LEN*len(nHops))
else:
secretRNG = paddingPRNG
+ # Determine exit routing for path1 and path2.
if reply:
- path1exitnode = reply[1]
- path2exit = None
- reply = reply[0]
+ path1exitinfo = reply.addr.getRoutingInfo().pack()
else:
- 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)
+ path1exitinfo = path2[0].getRoutingInfo().pack()
-#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.
+ # Pad the payload, as needed.
if len(payload) < PAYLOAD_LEN:
- payload += paddingRNG.getBytes(PAYLOAD_LEN-len(payload))
+ # ???? Payload padding/sizing must be handled in spec.
+ payload += paddingPRNG.getBytes(PAYLOAD_LEN-len(payload))
+ # Generate secrets for path1.
secrets1 = [ secretRNG.getBytes(SECRET_LEN) for x in range(len(path1)) ]
if path2:
- # Make secrets2 before header1 so we don't use the RNG to pad
- # the first header yet.
+ # Make secrets for header 2, and construct header 2. We do the before
+ # making header1 so that our rng won't be used for padding yet.
secrets2 = [ secretRNG.getBytes(SECRET_LEN) for x in range(len(path2))]
- header2 = _buildHeader(path2,secrets2,path2exit[0],path2exit[1],
- paddingRNG)
+ header2 = _buildHeader(path2,secrets2,exitType,exitInfo,paddingPRNG)
else:
secrets2 = None
- header2 = reply
+ header2 = reply.header
+
+ # Construct header1.
+ header1 = _buildHeader(path1,secrets1,Modules.SWAP_FWD_TYPE,path1exitinfo,
+ paddingPRNG)
- header1 = _buildHeader(path1,secrets1,path1exit[0],path1exit[1],
- paddingRNG)
return _constructMessage(secrets1, secrets2, header1, header2, payload)
def _buildHeader(path,secrets,exitType,exitInfo,paddingPRNG):
- "XXXX"
- # XXXX insist on sane parameters, path lengths.
- routing = []
- for i in range(len(path)-1):
- nextNode = path[i+1]
- info = nextNode.getRoutingInfo()
- routing.append( (Modules.FWD_TYPE, info.pack() ) )
-
- routing.append( (exitType, exitInfo) )
-
- return _buildHeader_impl(path,secrets,routing,paddingPRNG)
+ """_buildHeader(path, secrets, exitType, exitInfo, paddingPRNG) -> str
-#routing is list of (type,info)
-def _buildHeader_impl(path, secrets, routing, paddingPRNG):
- "XXXX"
- hops = len(path)
+ Helper method to construct a single header.
+ path: A sequence of serverinfo objects.
+ secrets: A list of 16-byte strings to use as master-secrets for
+ each of the subeaders.
+ exitType: The routing for the last node in the header
+ exitInfo: The routing info for the last node in the header
+ paddingPRNG: A pseudo-random number generator to generate padding"""
+
+ assert len(path) == len(secrets)
+ if len(path) * ENC_SUBHEADER_LEN > HEADER_LEN:
+ raise MixError("Too many nodes in path")
+ # Construct a list 'routing' of exitType, exitInfo.
+ routing = [ (Modules.FWD_TYPE, node.getRoutingInfo().pack()) for
+ node in path[1:] ]
+ routing.append( (exitType, exitInfo) )
+
# sizes[i] is size, in blocks, of subheaders for i.
sizes =[ getTotalBlocksForRoutingInfoLen(len(info)) for t, info in routing]
+
# totalSize is number total number of blocks.
totalSize = reduce(operator.add, sizes)
+ if totalSize * ENC_SUBHEADER_LEN > HEADER_LEN:
+ raise MixError("Routing info won't fit in header")
# headerKey[i]==the AES key object node i will use to decrypt the header
headerKeys = [ Crypto.Keyset(secret).get(Crypto.HEADER_SECRET_MODE)
@@ -153,17 +217,16 @@
# 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
+ lastJunk = junkSeen[-1]
+ nextJunk = lastJunk + newJunk
# 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)]
+ # All of its extended subheaders [(size-1)*128]
+ # Non-junk parts of the header [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
+ # HEADER_LEN-len(lastJunk)-128.
+ startIdx = HEADER_LEN-len(lastJunk)-128
nextJunk = Crypto.ctr_crypt(nextJunk, headerKey, startIdx)
junkSeen.append(nextJunk)
@@ -171,15 +234,17 @@
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):
+ for i in range(len(path)-1, -1, -1):
rt, ri = routing[i]
+ # Create a subheader object for this node, but don't fill in the
+ # digest until we've constructed the rest of the header.
subhead = Subheader(MAJOR_NO, MINOR_NO,
secrets[i], " "*20,
rt, ri)
extHeaders = "".join(subhead.getExtraBlocks())
- rest = Crypto.ctr_crypt("".join((extHeaders,header)), headerKeys[i])
+ rest = Crypto.ctr_crypt(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)
@@ -187,27 +252,36 @@
return header
-# For a reply, secrets2==None
def _constructMessage(secrets1, secrets2, header1, header2, payload):
- "XXXX"
- #XXXX comment
+ """Helper method: Builds a message, given both headers, all known
+ secrets, and the padded payload.
+
+ If using a reply block, secrets2 should be null."""
assert len(payload) == PAYLOAD_LEN
assert len(header1) == len(header2) == HEADER_LEN
- secrets1 = secrets1[:]
- if secrets2:
- secrets2 = secrets2[:]
+
if secrets2:
+ # (Copy secrets2 so we don't reverse the original)
+ secrets2 = secrets2[:]
+
+ # If we're not using a reply block, encrypt the payload for
+ # each key in the second path, in reverse order.
secrets2.reverse()
for secret in secrets2:
ks = Crypto.Keyset(secret)
key = ks.getLionessKeys(Crypto.PAYLOAD_ENCRYPT_MODE)
payload = Crypto.lioness_encrypt(payload, key)
+ # Encrypt header2 with a hash of the payload.
key = Crypto.lioness_keys_from_payload(payload)
header2 = Crypto.lioness_encrypt(header2, key)
- secrets1.reverse()
+ # Copy secrets1 so we don't reverse the original.
+ secrets1 = secrets1[:]
+
+ # Now, encrypt header2 and the payload for each node in path1, reversed.
+ secrets1.reverse()
for secret in secrets1:
ks = Crypto.Keyset(secret)
hkey = ks.getLionessKeys(Crypto.HEADER_ENCRYPT_MODE)
Index: Common.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Common.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- Common.py 31 May 2002 12:47:58 -0000 1.2
+++ Common.py 2 Jun 2002 06:11:16 -0000 1.3
@@ -1,11 +1,16 @@
# Copyright 2002 Nick Mathewson. See LICENSE for licensing information.
# $Id$
+"""mixminion.Common
+
+ Common functionality and utility code for Mixminion"""
+
+__all__ = [ 'MixError', 'MixFatalError' ]
+
class MixError(Exception):
+ """Base exception class for all Mixminion errors"""
pass
class MixFatalError(MixError):
+ """Exception class for unrecoverable Mixminion errors."""
pass
-
-
-
Index: Crypto.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Crypto.py,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- Crypto.py 31 May 2002 12:47:58 -0000 1.5
+++ Crypto.py 2 Jun 2002 06:11:16 -0000 1.6
@@ -11,7 +11,8 @@
import sys
import mixminion._minionlib as _ml
-__all__ = [ 'init_crypto', 'sha1', 'ctr_crypt', 'prng', 'strxor',
+__all__ = [ 'SSLError',
+ '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',
@@ -21,6 +22,8 @@
'HEADER_ENCRYPT_MODE', 'APPLICATION_KEY_MODE',
'PAYLOAD_ENCRYPT_MODE', 'HIDE_HEADER_MODE' ]
+SSLError = _ml.SSLError
+
AES_KEY_LEN = 128/8
DIGEST_LEN = 160/8
@@ -60,13 +63,13 @@
Given a string s and a 16-byte key key, computes the AES counter-mode
encryption of s using k. The counter begins at idx."""
- if type(key) == str:
+ if type(key) == type(""):
key = _ml.aes_key(key)
return _ml.aes_ctr128_crypt(key,s,idx)
def prng(key,count,idx=0):
"""Returns the bytestream 0x00000000...., encrypted in counter mode."""
- if type(key) == str:
+ if type(key) == type(""):
key = _ml.aes_key(key)
return _ml.aes_ctr128_crypt(key,"",idx,count)
@@ -78,19 +81,19 @@
assert len(key) == 4
key1,key2,key3,key4 = key
- assert len(key1)==len(key3)==20
- assert len(key2)==len(key4)==20
- assert len(s) > 20
+ assert len(key1)==len(key3)==DIGEST_LEN
+ assert len(key2)==len(key4)==DIGEST_LEN
+ assert len(s) > DIGEST_LEN
- left = s[:20]
- right = s[20:]
+ left = s[:DIGEST_LEN]
+ right = s[DIGEST_LEN:]
del s
# Performance note: This business with sha1("".join([key,right,key]))
# may look slow, but it contributes only a 6% to the hashing step,
# which in turn contributes under 11% of the time for LIONESS.
- right = ctr_crypt(right, _ml.sha1("".join([key1,left,key1]))[:16])
+ right = ctr_crypt(right, _ml.sha1("".join([key1,left,key1]))[:AES_KEY_LEN])
left = _ml.strxor(left, _ml.sha1("".join([key2,right,key2])))
- right = ctr_crypt(right, _ml.sha1("".join([key3,left,key3]))[:16])
+ right = ctr_crypt(right, _ml.sha1("".join([key3,left,key3]))[:AES_KEY_LEN])
left = _ml.strxor(left, _ml.sha1("".join([key4,right,key4])))
return left + right
@@ -102,17 +105,17 @@
assert len(key) == 4
key1,key2,key3,key4 = key
- assert len(key1)==len(key3)==20
- assert len(key2)==len(key4)==20
- assert len(s) > 20
+ assert len(key1)==len(key3)==DIGEST_LEN
+ assert len(key2)==len(key4)==DIGEST_LEN
+ assert len(s) > DIGEST_LEN
- left = s[:20]
- right = s[20:]
+ left = s[:DIGEST_LEN]
+ right = s[DIGEST_LEN:]
del s
left = _ml.strxor(left, _ml.sha1("".join([key4,right,key4])))
- right = ctr_crypt(right, _ml.sha1("".join([key3,left,key3]))[:16])
+ right = ctr_crypt(right, _ml.sha1("".join([key3,left,key3]))[:AES_KEY_LEN])
left = _ml.strxor(left, _ml.sha1("".join([key2,right,key2])))
- right = ctr_crypt(right, _ml.sha1("".join([key1,left,key1]))[:16])
+ right = ctr_crypt(right, _ml.sha1("".join([key1,left,key1]))[:AES_KEY_LEN])
return left + right
def openssl_seed(count):
@@ -125,7 +128,7 @@
"""trng(count) -> str
Returns (count) bytes of true random data from a true source of
- entropy (/dev/urandom)"""
+ entropy (/dev/urandom). May read ahead and cache values."""
return _theTrueRNG.getBytes(count)
OAEP_PARAMETER = "He who would make his own liberty secure, "+\
@@ -228,6 +231,9 @@
#---------------------------------------------------------------------
class RNG:
+ '''Base implementation class for random number generators. Works
+ by requesting a bunch of bytes via self._prng, and doling them
+ out piecemeal via self.getBytes.'''
def __init__(self, chunksize):
self.bytes=""
self.chunksize=chunksize
@@ -246,27 +252,32 @@
assert 0
class AESCounterPRNG(RNG):
+ '''Pseudorandom number generator that yields an AES counter-mode cipher
+ stram.'''
def __init__(self, seed=None):
RNG.__init__(self, 16*1024)
self.counter = 0
if seed == None:
- seed = trng(16)
+ seed = trng(AES_KEY_LEN)
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):
+def _trng_uncached(n):
+ '''Underlying access to our true entropy source.'''
f = open('/dev/urandom')
d = f.read(n)
f.close()
return d
class _TrueRNG(RNG):
+ '''Random number generator that yields pieces of entropy from
+ our true rng.'''
def __init__(self,n):
RNG.__init__(self,n)
def _prng(self,n):
- return trng_uncached(n)
+ return _trng_uncached(n)
-_theTrueRNG = _TrueRNG(128)
+_theTrueRNG = _TrueRNG(1024)
Index: HashLog.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/HashLog.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- HashLog.py 31 May 2002 12:47:58 -0000 1.2
+++ HashLog.py 2 Jun 2002 06:11:16 -0000 1.3
@@ -1,11 +1,17 @@
# Copyright 2002 Nick Mathewson. See LICENSE for licensing information.
# $Id$
+"""mixminion.HashLog
+
+ Persistant memory for the hashed secrets we've seen."""
+
import anydbm
from mixminion.Common import MixFatalError
__all__ = [ 'HashLog' ]
+# FFFF Mechanism to force a different default db module.
+
class HashLog:
"""A HashLog is a file containing a list of message digests that we've
already processed.
@@ -29,6 +35,7 @@
Creates a new HashLog to store data in 'filename' for the key
'keyid'."""
self.log = anydbm.open(filename, 'c')
+ #FFFF Warn if we're using dumbdbm
try:
if self.log["KEYID"] != keyid:
raise MixFatalError("Log KEYID does not match current KEYID")
@@ -55,10 +62,13 @@
"""sync()
Flushes changes to this log to the filesystem."""
- self.log.sync()
+ if hasattr(self.log, "sync"):
+ self.log.sync()
def close(self):
"""close()
Closes this log."""
+ self.sync()
self.log.close()
+
Index: Modules.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Modules.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- Modules.py 29 May 2002 03:52:13 -0000 1.1
+++ Modules.py 2 Jun 2002 06:11:16 -0000 1.2
@@ -1,12 +1,18 @@
# Copyright 2002 Nick Mathewson. See LICENSE for licensing information.
# $Id$
-DROP_TYPE = 0x0000
-FWD_TYPE = 0x0001
-SWAP_FWD_TYPE = 0x0002
+"""mixminion.Modules
+ Type codes and dispatch functions for routing functionality."""
+
+# Numerically first exit type.
MIN_EXIT_TYPE = 0x0100
-SMTP_TYPE = 0x0100
-LOCAL_TYPE = 0x0101
+# Mixminion types
+DROP_TYPE = 0x0000 # Drop the current message
+FWD_TYPE = 0x0001 # Forward the msg to an IPV4 addr via MMTP
+SWAP_FWD_TYPE = 0x0002 # SWAP, then forward the msg to an IPV4 addr via MMTP
+# Exit types
+SMTP_TYPE = 0x0100 # Mail the message
+LOCAL_TYPE = 0x0101 # Store the message for local delivery.
Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -d -r1.3 -r1.4
--- ServerInfo.py 31 May 2002 12:47:58 -0000 1.3
+++ ServerInfo.py 2 Jun 2002 06:11:16 -0000 1.4
@@ -1,20 +1,26 @@
# Copyright 2002 Nick Mathewson. See LICENSE for licensing information.
# $Id$
-"XXXX"
+"""mixminion.ServerInfo
+
+ Data structures to represent a server's information, and functions to
+ martial and unmarshal it.
+
+ ???? Since we don't have an interchange format yet, we only have
+ an object with the minimal info."""
__all__ = [ 'ServerInfo' ]
from mixminion.Modules import SWAP_FWD_TYPE, FWD_TYPE
-from mixminion.Formats import IPV4Info
+from mixminion.Packet import IPV4Info
#
# Stub class till we have the real thing
#
class ServerInfo:
- "XXXX"
+ """Represents a Mixminion server, and the information needed to send
+ messages to it."""
def __init__(self, addr, port, modulus, keyid):
- "XXXX"
self.addr = addr
self.port = port
self.modulus = modulus
@@ -26,5 +32,6 @@
def getKeyID(self): return self.keyid
def getRoutingInfo(self, swap=0):
+ """Returns a mixminion.Packet.IPV4Info object for routing messages
+ to this server."""
return IPV4Info(self.addr, self.port, self.keyid)
-
Index: __init__.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/__init__.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- __init__.py 29 May 2002 03:52:13 -0000 1.1
+++ __init__.py 2 Jun 2002 06:11:16 -0000 1.2
@@ -1,4 +1,12 @@
# Copyright 2002 Nick Mathewson. See LICENSE for licensing information.
# $Id$
+"""mixminion
+
+ Client and server code for type III anonymous remailers.
+
+ XXXX write more on principal interfaces"""
+
__version__ = "0.1"
+
+# XXXX __all__
Index: benchmark.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/benchmark.py,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- benchmark.py 31 May 2002 12:47:58 -0000 1.4
+++ benchmark.py 2 Jun 2002 06:11:16 -0000 1.5
@@ -1,35 +1,53 @@
# Copyright 2002 Nick Mathewson. See LICENSE for licensing information.
# $Id$
+
+"""mixminion.benchmark
+
+ Performance tests for Mixminion functionality.
+
+ Usage:
+ >>> import mixminion.benchmark
+ >>> mixminion.benchmark.timeAll()
+
+ """
+
+
from time import time
__all__ = [ 'timeAll', 'testLeaks1', 'testLeaks2' ]
+# If PRECISION_FACTOR is >1, we time everything for PRECISION_FACTOR times
+# more iterations than ususal.
+#
+# If PRESISION_FACTOR is 0, we only try stuff once. Good for testing this
+# file in a hurry.
PRECISION_FACTOR = 1
-primitives = {}#XXXXDOC
+# Dictionary holds cached values of time for no-op loops.
loop_overhead = {}
-def timeit_(fn, iters, ov=1, p=None):
- """XXXX"""
+def timeit_(fn, iters, ov=1):
+ """timeit_(fn, iters)
+
+ returns the execution time for fn(), measures with iters sample
+ iterations."""
iters *= PRECISION_FACTOR
+ if iters < 1: iters = 1
nones = [None]*iters
- overhead = [0, loop_overhead.get(iters, 0)][ov]
+ if ov:
+ overhead = loop_overhead.get(iters, None)
+ if overhead == None:
+ overhead = loop_overhead[iters] = timeit_((
+ lambda:(lambda:None)()), iters, 0)
+ else:
+ overhead = 0
t = time()
for n in nones: fn()
t2 = time()-t
- t_each = (t2-overhead) / float(iters)
- if p: primitives[p]=t_each
+ t_each = ((t2) / float(iters))-overhead
return t_each
-min_o = 1.0
-max_o = 0.0
-for iters in [10**n for n in range(2,7)]:
- overhead = timeit_((lambda:(lambda:None)()), iters)
- loop_overhead[iters] = overhead
- min_o = min(min_o, overhead/float(iters))
- max_o = max(max_o, overhead/float(iters))
-
def timestr(t):
- """XXXX"""
+ """Given a time in seconds, returns a readable representation"""
if abs(t) >= 1.0:
return "%.3f sec" % t
elif abs(t) >= .001:
@@ -39,50 +57,51 @@
else:
return "%f psec" % (t*1000000000L)
-def timeit(fn,times, p=None):
- """XXXX"""
- return timestr(timeit_(fn,times,p=p))
+def timeit(fn,times):
+ """Same as timeit_, but returns a readable representation"""
+ return timestr(timeit_(fn,times))
def spacestr(n):
+ """Converts number of bytes to readable representation)"""
if abs(n) < 1e4:
return "%d bytes" %n
elif abs(n) < 1e7:
- return "%d KB" % (n//1024)
+ return "%d KB" % (n >> 10)
elif abs(n) < 1e10:
- return "%d MB" % (n//(1024*1024))
+ return "%d MB" % (n >> 20)
else:
- return "%d GB" % (n//(1024*1024*1024))
+ return "%d GB" % (n >> 30)
#----------------------------------------------------------------------
import mixminion._minionlib as _ml
from Crypto import *
from Crypto import OAEP_PARAMETER
+short = "Hello, Dali!"
+s20b = "ABCDEFGHIJKLMNOPQRST"
+s64b = "8charstr"*8
+s128b = s64b*2
+s70b = "10character"*7
+s1K = "8charstr"*128
+s2K = s1K*2
+s4K = s2K*2
+s8K = s4K*2
+s28K = s1K*28
+s32K = s8K*4
+
def cryptoTiming():
- short = "Hello, Dali!"
- s1K = "8charstr"*128
- s2K = s1K*2
- s4K = s2K*2
- s8K = s4K*2
- s28K = s1K*28
- s32K = s8K*4
print "#==================== CRYPTO ======================="
- 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 (64b)", timeit((lambda : sha1(s64b)), 100000)
+ print "SHA1 (2K)", timeit((lambda : sha1(s2K)), 10000)
print "SHA1 (8K)", timeit((lambda : sha1(s8K)), 10000)
- print "SHA1 (28K)", timeit((lambda : sha1(s28K)), 1000, p="SHA28")
+ print "SHA1 (28K)", timeit((lambda : sha1(s28K)), 1000)
print "SHA1 (32K)", timeit((lambda : sha1(s32K)), 1000)
shakey = "8charstr"*2
- #print "Keyed SHA1 (short)",
- #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 "Keyed SHA1 for lioness (28K, unoptimized)", timeit(
- (lambda : _ml.sha1("".join([shakey,s28K,shakey]))), 1000)
+ (lambda shakey=shakey: _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)
@@ -92,58 +111,75 @@
print "xor (32K)", timeit((lambda: _ml.strxor(s32K,s32K)), 1000)
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 (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)
+ print "aes (short)", timeit((lambda key=key: ctr_crypt(short,key)), 100000)
+ print "aes (1K)", timeit((lambda key=key: ctr_crypt(s1K,key)), 10000)
+ print "aes (2K)", timeit((lambda key=key: ctr_crypt(s2K,key)), 10000)
+ print "aes (28K)", timeit((lambda key=key: ctr_crypt(s28K,key)), 100)
+ print "aes (32K)", timeit((lambda key=key: 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 (short,pre-key)", \
+ timeit((lambda key=key: ctr_crypt(short,key)), 100000)
+ print "aes (1K,pre-key)", \
+ timeit((lambda key=key: ctr_crypt(s1K,key)), 10000)
+ print "aes (28K,pre-key)", \
+ timeit((lambda key=key: ctr_crypt(s28K,key)), 100)
+ print "aes (32K,pre-key)", \
+ timeit((lambda key=key: ctr_crypt(s32K,key)), 100)
print "aes (32K,pre-key,unoptimized)", timeit(
- (lambda: _ml.strxor(prng(key,32768),s32K)), 100)
+ (lambda key=key: _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 (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 (short)", timeit((lambda key=key: prng(key,8)), 100000)
+ print "prng (128b)", timeit((
+ lambda key=key: prng(key,18)), 10000)
+ print "prng (1K)", timeit((
+ lambda key=key: prng(key,1024)), 10000)
+ print "prng (2K)", timeit((
+ lambda key=key: prng(key,2048)), 10000)
+ print "prng (28K)", timeit((
+ lambda key=key: prng(key,28678)), 100)
+ print "prng (32K)", timeit((lambda key=key: prng(key,32768)), 100)
print "prng (32K, unoptimized)", timeit(
- (lambda: ctr_crypt('\x00'*32768, key)), 100)
+ (lambda key=key: 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,
- p="LIONESS2")
- print "lioness E (4K)", timeit((lambda: lioness_encrypt(s4K, lkey)), 1000)
- 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)
- 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)
+ print "lioness E (1K)", timeit((
+ lambda lkey=lkey: lioness_encrypt(s1K, lkey)), 1000)
+ print "lioness E (2K)", timeit((
+ lambda lkey=lkey: lioness_encrypt(s1K, lkey)), 1000)
+ print "lioness E (4K)", timeit((
+ lambda lkey=lkey: lioness_encrypt(s4K, lkey)), 1000)
+ print "lioness E (28K)", timeit((
+ lambda lkey=lkey: lioness_encrypt(s28K, lkey)), 100)
+ print "lioness E (32K)", timeit((
+ lambda lkey=lkey: lioness_encrypt(s32K, lkey)), 100)
+ print "lioness D (1K)", timeit((
+ lambda lkey=lkey: lioness_decrypt(s1K, lkey)), 1000)
+ print "lioness D (2K)", timeit((
+ lambda lkey=lkey: lioness_decrypt(s1K, lkey)), 1000)
+ print "lioness D (4K)", timeit((
+ lambda lkey=lkey: lioness_decrypt(s4K, lkey)), 1000)
+ print "lioness D (28K)", timeit((
+ lambda lkey=lkey: lioness_decrypt(s28K, lkey)), 100)
+ print "lioness D (32K)", timeit((
+ lambda lkey=lkey: lioness_decrypt(s32K, lkey)), 100)
- s70b = "10character"*7
print "OAEP_add (70->128B)",
print timeit((lambda: _ml.add_oaep_padding(s70b,OAEP_PARAMETER,128)),10000)
r = _ml.add_oaep_padding(s70b, OAEP_PARAMETER,128)
print "OAEP_check (128B->70B)",
- print timeit((lambda: _ml.check_oaep_padding(r,OAEP_PARAMETER,128)),10000)
+ print timeit((lambda r=r:
+ _ml.check_oaep_padding(r,OAEP_PARAMETER,128)),10000)
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,p="RSAENC")
+ print timeit((lambda rsa=rsa: pk_encrypt(s70b, rsa)),1000)
enc = pk_encrypt(s70b, rsa)
- print "Pad+RSA private decrypt", timeit((lambda: pk_decrypt(enc, rsa)),100)
+ print "Pad+RSA private decrypt", \
+ timeit((lambda enc=enc,rsa=rsa: pk_decrypt(enc, rsa)),100)
for (bits,it) in ((2048,10),(4096,10)):
t = time()
@@ -151,10 +187,16 @@
t = time()-t
print "RSA genrate (%d bit)"%bits, timestr(t)
enc = pk_encrypt(s70b, rsa2)
+ print "Pad+RSA public encrypt (%d bit)"%bits,
+ print timeit((lambda rsa2=rsa2: pk_encrypt("zzz", rsa2)),it)
print "Pad+RSA private decrypt (%d bit)"%bits,
- print timeit((lambda: pk_decrypt(enc, rsa2)),it)
+ print timeit((lambda enc=enc,rsa2=rsa2: pk_decrypt(enc, rsa2)),it)
+
+ o = loop_overhead.values()
+ print "Timing overhead: %s...%s" % (timestr(min(o)),timestr(max(o)))
#----------------------------------------------------------------------
+
def hashlogTiming():
import tempfile, os
print "#==================== HASH LOGS ======================="
@@ -163,17 +205,18 @@
try:
_hashlogTiming(fname,load)
finally:
- try:
- os.unlink(fname)
- except:
- pass
+ for suffix in ("", ".dat", ".bak", ".dir"):
+ try:
+ os.unlink(fname+suffix)
+ except OSError:
+ pass
def _hashlogTiming(fname, load):
- import os
+ import os, stat
from mixminion.Crypto import AESCounterPRNG
from mixminion.HashLog import HashLog
prng = AESCounterPRNG("a"*16)
-
+
h = HashLog(fname, "A")
hashes = [ prng.getBytes(20) for i in xrange(load) ]
@@ -196,24 +239,39 @@
t = time()-t
print "Check entry [miss] (%s entries)" %load, timestr( t/1000.0 )
+ hashes =[ prng.getBytes(20) for i in xrange(1000) ]
+ t = time()
+ for hash in hashes:
+ h.seenHash(hash)
+ h.logHash(hash)
+ t = time()-t
+ print "Check entry [miss+add] (%s entries)" %load, timestr( t/1000.0 )
+
h.close()
- print "File size (%s entries)"%load, spacestr(os.stat(fname).st_size)
+ size = 0
+ for suffix in ("", ".dat", ".bak", ".dir"):
+ if not os.path.exists(fname+suffix):
+ continue
+ size += os.stat(fname+suffix)[stat.ST_SIZE]
+
+ print "File size (%s entries)"%load, spacestr(size)
#----------------------------------------------------------------------
+import mixminion.BuildMessage as BMsg
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):
+ def bh(np,it, serverinfo=serverinfo):
ctr = AESCounterPRNG()
- tm = timeit_( \
- lambda: BMsg._buildHeader(serverinfo[:np], ["Z"*16]*np,
+ tm = timeit_(
+ lambda np=np,it=it,serverinfo=serverinfo,ctr=ctr:
+ BMsg._buildHeader(serverinfo[:np], ["Z"*16]*np,
99, "Hello", ctr), it )
print "Build header (%s)" %(np), timestr(tm)
@@ -223,9 +281,10 @@
bh(8,20)
bh(16,10)
- def bm(np1,np2,it):
+ def bm(np1,np2,it,serverinfo=serverinfo,payload=payload):
tm = timeit_( \
- lambda: BMsg.buildForwardMessage(payload,
+ lambda np1=np1,np2=np2,it=it,serverinfo=serverinfo,
+ payload=payload: BMsg.buildForwardMessage(payload,
1,
"Hello",
serverinfo[:np1],
@@ -238,33 +297,124 @@
bm(8,8,20)
bm(16,16,10)
#----------------------------------------------------------------------
+class DummyLog:
+ def seenHash(self,h): return 0
+ def logHash(self,h): pass
+
+from mixminion.PacketHandler import PacketHandler
+from mixminion.ServerInfo import ServerInfo
+from mixminion.BuildMessage import buildForwardMessage
+from mixminion.Modules import SMTP_TYPE
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)
+ sp = PacketHandler(pk, DummyLog())
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)
+ lambda sp=sp, m_noswap=m_noswap : 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)
+ lambda sp=sp, m_swap=m_swap : sp.processMessage(m_swap), 100)
+
+#----------------------------------------------------------------------
+def timeEfficiency():
+ print "#================= ACTUAL v. IDEAL ====================="
+
+ ##### LIONESS
+
+ shakey = "z"*20
+ aeskey = "p"*16
+ # Lioness_encrypt is:
+ # 2 28K SHA1's (keyed)
+ # 2 20b SHA1's (keyed)
+ # 2 20b string xors.
+ # 2 28K aes_crypts.
+ shastr = shakey+s28K+shakey
+ sha1_keyed_28k = timeit_((lambda shastr=shastr: _ml.sha1(shastr)), 1000)
+ shastr = shakey+s20b+shakey
+ sha1_keyed_20b = timeit_((lambda shastr=shastr: _ml.sha1(shastr)), 100000)
+ strxor_20b = timeit_((lambda s=s20b: _ml.strxor(s,s)), 100000)
+ aes_28k = timeit_((lambda s=s28K,k=aeskey: ctr_crypt(s,k)), 100)
+
+ lionesskey = ("p"*20,)*4
+ lioness_e = timeit_((lambda s=s28K,k=lionesskey: lioness_encrypt(s,k)),100)
+
+ expected = 2*(strxor_20b+aes_28k+sha1_keyed_28k+sha1_keyed_20b)
+ print "LIONESS TOOK:", timestr(lioness_e)
+ print " expected:", timestr(expected)
+ print " difference:", timestr(lioness_e-expected)
+ print " goodness: %3.2f%%" % (100*expected/lioness_e)
+ print " breakdown: aes: %3.1f%%" % (100*2*aes_28k/lioness_e)
+ print " long sha1: %3.1f%%" % (100*2*sha1_keyed_28k/lioness_e)
+ print " short sha1: %3.1f%%" % (100*2*sha1_keyed_20b/lioness_e)
+ print " short xor: %3.1f%%" % (100*2*strxor_20b/lioness_e)
+
+ ##### SERVER PROCESS
+ pk = pk_generate(1024)
+
+ # Typical (no swap) server process is:
+ # pk_decrypt (128b)
+ # sha1 (2K-128b)
+ #5*sha1 (16b+~16b) [HEADER_SEC,HEADER_ENC,PRNG,PAYLOAD_ENC,REPLAY]
+ # hashlog.seen **omit
+ # hashlog.log **omit
+ # ctr_crypt (2K)
+ # lioness_D (28K)
+ # prng (128b)
+ # lioness_D (2K)
+
+ # With swap, add:
+ # keys_from_payload=HASH(28K)
+ # lioness_D (2K)
+
+ enc = pk_encrypt(s70b, pk)
+ rsa_128b = timeit_((lambda pk=pk,enc=enc: pk_decrypt(enc,pk)), 100)
+ shastr = s2K[2048-128]
+ sha1_hdr = timeit_((lambda shastr=shastr: sha1(shastr)), 10000)
+ shastr = s64b[:32]
+ sha1_key = timeit_((lambda shastr=shastr: sha1(shastr)), 10000)
+ aes_2k = timeit_((lambda k=aeskey: ctr_crypt(s2K,k)), 1000)
+ lioness_28k = lioness_e
+ lioness_2k = timeit_((
+ lambda s=s2K,k=lionesskey: lioness_encrypt(s,k)),1000)
+ prng_128b = timeit_((lambda k=aeskey: prng(k,128)),10000)
+
+ n = pk_get_modulus(pk)
+ server = ServerInfo("127.0.0.1", 1, n, "X"*20)
+ sp = PacketHandler(pk, DummyLog())
+
+ m_noswap = buildForwardMessage("Hello world", SMTP_TYPE, "f@invalid",
+ [server, server], [server, server])
+
+ sp_ns = timeit_(
+ lambda sp=sp, m_noswap=m_noswap : sp.processMessage(m_noswap), 100)
+
+ expected = rsa_128b+sha1_hdr+sha1_key*5+aes_2k+lioness_28k+prng_128b
+ expected += lioness_2k
+ print "SERVERPROCESS TOOK:", timestr(sp_ns)
+ print " expected:", timestr(expected)
+ print " difference:", timestr(sp_ns-expected)
+ print " goodness: %3.2f%%" % (100*expected/sp_ns)
+ print " breakdown: rsa: %3.1f%%" % (100*rsa_128b/sp_ns)
+ print " 28K lioness: %3.1f%%" % (100*lioness_28k/sp_ns)
+ print " 2K lioness: %3.1f%%" % (100*lioness_2k/sp_ns)
+ print " header aes: %3.1f%%" % (100*aes_2k/sp_ns)
+ print " header sha1: %3.1f%%" % (100*sha1_hdr/sp_ns)
+ print " keygen sha1: %3.1f%%" % (500*sha1_key/sp_ns)
+ print " (logs not included)"
+ # FFFF Time the logs too..?
+
+ # FFFF It would be nice to time BuildMessage, but less critical.
#----------------------------------------------------------------------
def testLeaks1():
@@ -329,6 +479,7 @@
buildMessageTiming()
hashlogTiming()
serverProcessTiming()
+ timeEfficiency()
if __name__ == '__main__':
timeAll()
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- test.py 31 May 2002 12:47:58 -0000 1.5
+++ test.py 2 Jun 2002 06:11:16 -0000 1.6
@@ -1,12 +1,29 @@
# Copyright 2002 Nick Mathewson. See LICENSE for licensing information.
# $Id$
-import unittest
+"""mixminion.tests
+
+ Unit tests for all Mixminion functionality.
+
+ Usage:
+ >>> import mixminion.tests
+ >>> mixminion.tests.testAll()
+
+ """
+
+import os
+from mixminion.Common import MixError
+
+try:
+ import unittest
+except ImportError:
+ import mixminion._unittest as unittest
def hexread(s):
+ assert (len(s) % 2) == 0
r = []
hexvals = "0123456789ABCDEF"
- for i in range(len(s) // 2):
+ for i in range(len(s) / 2):
v1 = hexvals.index(s[i*2])
v2 = hexvals.index(s[i*2+1])
c = (v1 << 4) + v2
@@ -14,6 +31,16 @@
r.append(chr(c))
return "".join(r)
+def try_unlink(fname):
+ try:
+ os.unlink(fname)
+ except OSError:
+ pass
+
+def try_unlink_db(fname):
+ for suffix in ("", ".bak", ".dat", ".dir"):
+ try_unlink(fname+suffix)
+
#----------------------------------------------------------------------
import mixminion._minionlib as _ml
@@ -127,7 +154,6 @@
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):
@@ -214,6 +240,8 @@
# 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))
+ self.assertEquals(pk_get_modulus(k512),
+ pk_get_modulus(pk_from_modulus(pk_get_modulus(k512))))
# Make sure that public keys can be made from moduli, and used to
# encrypt and decrypt.
@@ -299,8 +327,8 @@
PRNG.getBytes(34764)))
#----------------------------------------------------------------------
-import mixminion.Formats
-from mixminion.Formats import *
+import mixminion.Packet
+from mixminion.Packet import *
class FormatTests(unittest.TestCase):
def test_subheader(self):
@@ -310,11 +338,13 @@
expected = "\003\000abcdeabcdeabcdef"+\
"ABCDEFGHIJABCDEFGHIJ\000\005\000\001Hello"
+ # test packing
self.assertEquals(s.pack(), expected)
self.failUnless(not s.isExtended())
self.assertEquals(s.getNExtraBlocks(), 0)
self.assertEquals(s.getExtraBlocks(), [])
+ # test unpacking,
s = parseSubheader(s.pack())
self.assertEquals(s.major, 3)
self.assertEquals(s.minor, 0)
@@ -337,8 +367,9 @@
self.assertEquals(len(ts_eliot), 186)
+ # test extended subeaders
expected = "\003\011abcdeabcdeabcdefABCDEFGHIJABCDEFGHIJ\000\272\000\076Who is the third who walks always beside you"
- self.assertEquals(len(expected), mixminion.Formats.MAX_SUBHEADER_LEN)
+ self.assertEquals(len(expected), mixminion.Packet.MAX_SUBHEADER_LEN)
self.assertEquals(s.pack(), expected)
extra = s.getExtraBlocks()
@@ -348,6 +379,7 @@
"road / There is always another one walk")
self.assertEquals(extra[1], "ing beside you"+(114*'\000'))
+ # test parsing extended subheaders
s = parseSubheader(expected)
self.assertEquals(s.major, 3)
self.assertEquals(s.minor, 9)
@@ -363,8 +395,12 @@
self.assertEquals(s.pack(), expected)
self.assertEquals(s.getExtraBlocks(), extra)
+ # Underlong subheaders must fail
self.failUnlessRaises(ParseError,
parseSubheader, "a"*(41))
+ # overlong subheaders must fail
+ self.failUnlessRaises(ParseError,
+ parseSubheader, "a"*(99))
def test_headers(self):
header = ("abcdefghi"*(256))[:2048]
@@ -375,6 +411,8 @@
self.failUnless(h[1:] == header[128:])
self.failUnless(h[1:4] == header[128:128*4])
self.failUnless(h[15] == header[-128:])
+ self.failUnless(h[15] == h[-1])
+ self.failUnless(h[14:] == h[-2:])
def test_message(self):
# 9 is relatively prime to all pwrs of 2.
@@ -435,17 +473,14 @@
try:
self.hashlogTestImpl(fname)
finally:
- try:
- os.unlink(fname)
- except:
- pass
+ try_unlink_db(fname)
def hashlogTestImpl(self,fname):
- h = HashLog(fname, "Xyzzy")
+ h = [HashLog(fname, "Xyzzy")]
- notseen = lambda hash: self.assert_(not h.seenHash(hash))
- seen = lambda hash: self.assert_(h.seenHash(hash))
- log = lambda hash: h.logHash(hash)
+ notseen = lambda hash,self=self,h=h: self.assert_(not h[0].seenHash(hash))
+ seen = lambda hash,self=self,h=h: self.assert_(h[0].seenHash(hash))
+ log = lambda hash,h=h: h[0].logHash(hash)
notseen("a")
notseen("a*20")
@@ -475,8 +510,8 @@
log("abcdef"*4)
seen("abcdef"*4)
- h.close()
- h = HashLog(fname, "Xyzzy")
+ h[0].close()
+ h[0] = HashLog(fname, "Xyzzy")
seen("a")
seen("b")
seen("\277"*20)
@@ -490,12 +525,11 @@
log("ddddd")
seen("ddddd")
- h.close()
- h = HashLog(fname, "Xyzzy")
+ h[0].close()
+ h[0] = HashLog(fname, "Xyzzy")
seen("ddddd")
- h.close()
-
+ h[0].close()
#----------------------------------------------------------------------
import mixminion.BuildMessage
@@ -515,9 +549,10 @@
BMTSupport = _BMTSupport()
+from mixminion.ServerInfo import ServerInfo
+
class BuildMessageTests(unittest.TestCase):
def setUp(self):
- from mixminion.ServerInfo import ServerInfo
self.pk1 = BMTSupport.pk1
self.pk2 = BMTSupport.pk2
self.pk3 = BMTSupport.pk3
@@ -544,7 +579,7 @@
head = bhead([self.server1, self.server2],
["9"*16, "1"*16], 99, "Hi mom", AESCounterPRNG())
- ipv4 = mixminion.Formats.IPV4Info
+ ipv4 = mixminion.Packet.IPV4Info
self.do_header_test(head,
(self.pk1, self.pk2),
["9"*16, "1"*16],
@@ -560,26 +595,39 @@
99, "Hi mom", AESCounterPRNG())
pks = (self.pk1,self.pk2,self.pk3)
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(),
+ rinfo = (mixminion.Packet.IPV4Info("127.0.0.2", 3, "Z"*20).pack(),
+ mixminion.Packet.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):
+ """Unwraps and checks the layers of a single header.
+ head: the header to check
+ pks: sequence of public keys for hops in the path
+ secrets: sequence of master secrets for the subheaders
+ rtypes: sequenece of expected routing types
+ rinfo: sequenece of expected routing info's.
+
+ If secrets is None, takes secrets from the headers without
+ checking.
+
+ Returns a list of the secrets encountered.
+ If rinfo is None, also returns a list of the routinginfo objects.
+ """
retsecrets = []
retinfo = []
if secrets == None:
secrets = [ None ] * len(pks)
- self.assertEquals(len(head), mixminion.Formats.HEADER_LEN)
+ self.assertEquals(len(head), mixminion.Packet.HEADER_LEN)
for pk, secret, rt, ri in zip(pks, secrets,rtypes,rinfo):
- subh = mixminion.Formats.parseSubheader(pk_decrypt(head[:128], pk))
+ subh = mixminion.Packet.parseSubheader(pk_decrypt(head[:128], pk))
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.major, mixminion.Packet.MAJOR_NO)
+ self.assertEquals(subh.minor, mixminion.Packet.MINOR_NO)
self.assertEquals(subh.digest, sha1(head[128:]))
self.assertEquals(subh.routingtype, rt)
@@ -595,7 +643,7 @@
size = 128
n = 0
else:
- self.assert_(len(ri) > mixminion.Formats.MAX_ROUTING_INFO_LEN)
+ self.assert_(len(ri) > mixminion.Packet.MAX_ROUTING_INFO_LEN)
n = subh.getNExtraBlocks()
size = (1+n)*128
more = ctr_crypt(head[128:128+128*n], key)
@@ -615,7 +663,7 @@
def test_extended_routinginfo(self):
bhead = mixminion.BuildMessage._buildHeader
- bhead_impl = mixminion.BuildMessage._buildHeader_impl
+
secrets = ["9"*16 ]
longStr = "Foo"*50
head = bhead([self.server1 ], secrets, 99, longStr, AESCounterPRNG())
@@ -625,22 +673,34 @@
self.do_header_test(head, pks, secrets, rtypes, rinfo)
- secrets.append("1"*16)
- longStr2 = longStr*2
+ # Now try a header with extended **intermediate** routing info.
+ # Since this never happens in the wild, we fake it.
+ longStr2 = longStr * 2
- head = bhead_impl([self.server1,self.server2], secrets,
- [ (99,longStr2) , (99,longStr) ], AESCounterPRNG())
+ def getLongRoutingInfo(longStr2=longStr2):
+ return LocalInfo("fred",longStr2)
- pks = (self.pk1,self.pk2)
- rtypes = (99,99)
- rinfo = (longStr2,longStr)
+ server4 = ServerInfo("127.0.0.1", 1, pk_get_modulus(self.pk1), "X"*20)
+ server4.getRoutingInfo = getLongRoutingInfo
+
+ secrets.append("1"*16)
+ head = bhead([self.server2, server4], secrets, 99, longStr,
+ AESCounterPRNG())
+ pks = (self.pk2,self.pk1)
+ rtypes = (FWD_TYPE,99)
+ rinfo = ("fred\000"+longStr2,longStr)
self.do_header_test(head, pks, secrets, rtypes, rinfo)
+ # Now we make sure that overlong routing info fails.
+ self.failUnlessRaises(MixError, bhead,
+ [self.server2, server4], secrets, 99, "Z"*2048,
+ AESCounterPRNG())
+
def test_constructmessage(self):
consMsg = mixminion.BuildMessage._constructMessage
- h1 = "abcdefgh"*(2048//8)
- h2 = "aBcDeFgH"*(2048//8)
+ h1 = "abcdefgh"*(2048/8)
+ h2 = "aBcDeFgH"*(2048/8)
######
### non-reply case
@@ -656,8 +716,8 @@
message = consMsg(secrets1, secrets2, h1, h2, pld)
- self.assertEquals(len(message), mixminion.Formats.MESSAGE_LEN)
- msg = mixminion.Formats.parseMessage(message)
+ self.assertEquals(len(message), mixminion.Packet.MESSAGE_LEN)
+ msg = mixminion.Packet.parseMessage(message)
head1, head2, payload = msg.header1, msg.header2, msg.payload
self.assert_(h1 == head1)
@@ -680,8 +740,8 @@
######
### Reply case
message = consMsg(secrets1, None, h1, h2, pld)
- self.assertEquals(len(message), mixminion.Formats.MESSAGE_LEN)
- msg = mixminion.Formats.parseMessage(message)
+ self.assertEquals(len(message), mixminion.Packet.MESSAGE_LEN)
+ msg = mixminion.Packet.parseMessage(message)
head1, head2, payload = msg.header1, msg.header2, msg.payload
self.assert_(h1 == head1)
@@ -698,9 +758,18 @@
self.assert_(payload == pld)
def do_message_test(self, msg,
- header_info_1, #XXXX doc
+ header_info_1,
header_info_2,
payload):
+ """Decrypts the layers of a message one by one, checking them for
+ correctness.
+ msg: the message to examine
+ header_info_1: a tuple of (pks,secrets,rtypes,rinfo)
+ as used by do_header_test for the first header.
+ header_info_2: a tuple of (pks,secrets,rtypes,rinfo)
+ as used by do_header_test for the second header.
+ payload: The beginning of the expected decrypted payload.
+ """
# Check header 1, and get secrets
sec = self.do_header_test(msg[:2048], *header_info_1)
h2 = msg[2048:4096]
@@ -717,7 +786,7 @@
ks = Keyset(s)
p = lioness_decrypt(p,ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE))
- # XXXX Need to do something about size encoding.
+ # ???? Need to do something about size encoding.
self.assertEquals(payload, p[:len(payload)])
@@ -759,7 +828,7 @@
brm = mixminion.BuildMessage.buildReplyMessage
## Stateful reply blocks.
- (rb, node1), secrets = \
+ reply, secrets = \
brb([ self.server3, self.server1, self.server2,
self.server1, self.server3 ],
SMTP_TYPE,
@@ -770,11 +839,11 @@
self.server1.getRoutingInfo().pack(),
self.server3.getRoutingInfo().pack())
- self.assert_(node1 is self.server3)
+ self.assert_(reply.addr == self.server3)
m = brm("Information?",
[self.server3, self.server1],
- (rb,node1))
+ reply)
self.do_message_test(m,
((self.pk3, self.pk1), None,
@@ -789,12 +858,12 @@
"Information?")
## Stateless replies
- rb,node1 = bsrb([ self.server3, self.server1, self.server2,
- self.server1, self.server3 ],
- "fred", "Galaxy Far Away.", 1)
+ reply = bsrb([ self.server3, self.server1, self.server2,
+ self.server1, self.server3 ],
+ "fred", "Galaxy Far Away.", 0)
- sec,(loc,) = self.do_header_test(rb, pks_1, None,
- (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,SMTP_TYPE),
+ sec,(loc,) = self.do_header_test(reply.header, pks_1, None,
+ (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,LOCAL_TYPE),
infos+(None,))
s = "fred\x00RTRN"
self.assert_(loc.startswith(s))
@@ -803,10 +872,10 @@
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,
+ reply = bsrb([ self.server3, self.server1, self.server2,
+ self.server1, self.server3 ],
+ "fred", None)
+ sec,(loc,) = self.do_header_test(reply.header, pks_1, None,
(FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,LOCAL_TYPE),
infos+(None,))
self.assert_(loc.startswith(s))
@@ -815,15 +884,14 @@
self.assert_(sec == [ prng.getBytes(16) for i in range(5)])
#----------------------------------------------------------------------
-# Having tested BuildMessage without using ServerProcess, we can now use
-# BuildMessage to see whether ServerProcess is doing the right thing.
+# Having tested BuildMessage without using PacketHandler, we can now use
+# BuildMessage to see whether PacketHandler is doing the right thing.
#
# (of course, we still need to build failing messages by hand)
-class ServerProcessTests(unittest.TestCase):
+class PacketHandlerTests(unittest.TestCase):
def setUp(self):
- from mixminion.ServerProcess import ServerProcess
- from mixminion.ServerInfo import ServerInfo
+ from mixminion.PacketHandler import PacketHandler
from tempfile import mktemp
self.pk1 = BMTSupport.pk1
self.pk2 = BMTSupport.pk2
@@ -836,20 +904,27 @@
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)
+ self.sp1 = PacketHandler(self.pk1, h)
+ self.sp2 = PacketHandler(self.pk2, h)
+ self.sp3 = PacketHandler(self.pk3, h)
+ self.sp2_3 = PacketHandler((self.pk2,self.pk3), h)
def tearDown(self):
- import os
- try:
- os.unlink(self.tmpfile)
- except:
- pass
+ self.hlog.close()
+ try_unlink_db(self.tmpfile)
- def do_test_chain(self, m, sps, routingtypes, routinginfo, payload):
+ def do_test_chain(self, m, sps, routingtypes, routinginfo, payload,
+ appkey=None):
+ """Routes a message through a series of servers, making sure that
+ each one decrypts it properly and routes it correctly to the
+ next.
+ m: the message to test
+ sps: sequence of PacketHandler objects for m's path
+ routingtypes: sequence of expected routingtype
+ routinginfo: sequence of expected routinginfo
+ payload: beginning of expected final payload."""
for sp, rt, ri in zip(sps,routingtypes,routinginfo):
- res = sp._processMessage(m)
+ res = sp.processMessage(m)
self.assertEquals(len(res), 2)
if rt in (FWD_TYPE, SWAP_FWD_TYPE):
self.assertEquals(res[0], "QUEUE")
@@ -860,7 +935,8 @@
self.assertEquals(res[0], "EXIT")
self.assertEquals(res[1][0], rt)
self.assertEquals(res[1][1], ri)
- # XXXX TEST application key
+ if appkey:
+ self.assertEquals(appkey, res[1][2])
self.assert_(res[1][3].startswith(payload))
break
@@ -880,9 +956,8 @@
p)
# A one-hop/one-hop message.
- m = bfm(p, SMTP_TYPE, "nobody@invalid",
- [self.server1], [self.server3])
-
+ m = bfm(p, SMTP_TYPE, "nobody@invalid", [self.server1], [self.server3])
+
self.do_test_chain(m,
[self.sp1,self.sp3],
[FWD_TYPE, SMTP_TYPE],
@@ -890,6 +965,12 @@
"nobody@invalid"],
p)
+ # Try servers with multiple keys
+ m = bfm(p, SMTP_TYPE, "nobody@invalid", [self.server2], [self.server3])
+ self.do_test_chain(m, [self.sp2_3, self.sp2_3], [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"
@@ -910,63 +991,118 @@
longemail],
p)
-
def test_rejected(self):
bfm = mixminion.BuildMessage.buildForwardMessage
brm = mixminion.BuildMessage.buildReplyMessage
brb = mixminion.BuildMessage.buildReplyBlock
- from mixminion.ServerProcess import ContentError
+ from mixminion.PacketHandler 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()
+ server1X.getRoutingInfo = lambda _packable=_packable : _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)
+ 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)
+ 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)
+ reply,s = brb([self.server3], SMTP_TYPE, "fred@invalid")
+ m = brm("Y", [self.server2], reply)
+ m2 = brm("Y", [self.server1], reply)
+ 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)
+ reply1,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)
+ reply2,s = brb([self.server2], LOCAL_TYPE, "foo",prng)
+ m = brm("Y", [self.server3], reply1)
+ m2 = brm("Y", [self.server3], reply2)
+ 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)
+ 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
+ # Wrong server.
+ m = bfm("Z", DROP_TYPE, "", [self.server1], [self.server2])
+ self.failUnlessRaises(SSLError, self.sp2.processMessage, m)
+ self.failUnlessRaises(SSLError, self.sp2_3.processMessage, m)
+
+ # Plain junk in header
+ m_x = ("XY"*64)+m[128:]
+ self.failUnlessRaises(SSLError, self.sp1.processMessage, m_x)
+
+ # Bad message length
+ m_x = m+"Z"
+ self.failUnlessRaises(ParseError, self.sp1.processMessage, m_x)
+
+ # Bad internal type
+ m_x = bfm("Z", 50, "", [self.server1], [self.server2])
+ q, (a,m_x) = self.sp1.processMessage(m_x)
+ self.failUnlessRaises(ContentError, self.sp2.processMessage, m_x)
+
+ # Subhead we can't parse
+ m_x = pk_encrypt("foo", self.pk1)+m[128:]
+ self.failUnlessRaises(ParseError, self.sp1.processMessage, m_x)
+
+ # Bad IPV4 info
+ subh_real = pk_decrypt(m[:128], self.pk1)
+ subh = parseSubheader(subh_real)
+ subh.setRoutingInfo(subh.routinginfo + "X")
+ m_x = pk_encrypt(subh.pack(), self.pk1)+m[128:]
+ self.failUnlessRaises(ParseError, self.sp1.processMessage, m_x)
+
+ # Subhead that claims to be impossibly long: FWD case
+ subh = parseSubheader(subh_real)
+ subh.setRoutingInfo("X"*100)
+ m_x = pk_encrypt(subh.pack(), self.pk1)+m[128:]
+ self.failUnlessRaises(ContentError, self.sp1.processMessage, m_x)
+
+ # Subhead that claims to be impossibly long: exit case
+ subh = parseSubheader(subh_real)
+ subh.routingtype = LOCAL_TYPE
+ subh.setRoutingInfo("X"*10000)
+ m_x = pk_encrypt(subh.pack(), self.pk1)+m[128:]
+ self.failUnlessRaises(ContentError, self.sp1.processMessage, m_x)
+
+ # Bad Major or Minor
+ subh = parseSubheader(subh_real)
+ subh.major = 255
+ m_x = pk_encrypt(subh.pack(), self.pk1)+m[128:]
+ self.failUnlessRaises(ContentError, self.sp1.processMessage, m_x)
+
+ # Bad digest
+ subh = parseSubheader(subh_real)
+ subh.digest = " "*20
+ m_x = pk_encrypt(subh.pack(), self.pk1)+m[128:]
+ self.failUnlessRaises(ContentError, self.sp1.processMessage, m_x)
+
+ # Corrupt payload
+ m = bfm("Z", LOCAL_TYPE, "Z", [self.server1, self.server2],
+ [self.server3])
+ m_x = m[:-30] + " "*30
+ assert len(m_x) == len(m)
+ q, (a, m_x) = self.sp1.processMessage(m_x)
+ q, (a, m_x) = self.sp2.processMessage(m_x)
+ self.failUnlessRaises(SSLError, self.sp3.processMessage, m_x)
#----------------------------------------------------------------------
@@ -979,7 +1115,7 @@
suite.addTest(tc(FormatTests))
suite.addTest(tc(HashLogTests))
suite.addTest(tc(BuildMessageTests))
- suite.addTest(tc(ServerProcessTests))
+ suite.addTest(tc(PacketHandlerTests))
return suite
def testAll():
--- Formats.py DELETED ---
--- ServerProcess.py DELETED ---