def processPacket(self, msg): """Given a 32K mixminion packet, processes it completely. Return one of: None [if the packet should be dropped.] a DeliveryPacket object a RelayedPacket object May raise CryptoError, ParseError, or ContentError if the packet is malformatted, misencrypted, unparseable, repeated, or otherwise unhandleable. WARNING: This implementation does nothing to prevent timing attacks: dropped packets, packets with bad digests, replayed packets, and exit packets are all processed faster than forwarded packets. You must prevent timing attacks elsewhere.""" # Break into headers and payload pkt = Packet.parsePacket(msg) header1 = Packet.parseHeader(pkt.header1) encSubh = header1[:Packet.ENC_SUBHEADER_LEN] header1 = header1[Packet.ENC_SUBHEADER_LEN:] assert len(header1) == Packet.HEADER_LEN - Packet.ENC_SUBHEADER_LEN assert len(header1) == (128*16) - 256 == 1792 # Try to decrypt the first subheader. Try each private key in # order. Only fail if all private keys fail. subh = None e = None self.lock.acquire() try: for pk, hashlog in self.privatekeys: try: subh = Crypto.pk_decrypt(encSubh, pk) break except Crypto.CryptoError, err: e = err finally: self.lock.release() if not subh: # Nobody managed to get us the first subheader. Raise the # most-recently-received error. raise e if len(subh) != Packet.MAX_SUBHEADER_LEN: raise ContentError("Bad length in RSA-encrypted part of subheader") subh = Packet.parseSubheader(subh) #may raise ParseError # Check the version: can we read it? if subh.major != Packet.MAJOR_NO or subh.minor != Packet.MINOR_NO: raise ContentError("Invalid protocol version") # Check the digest of all of header1 but the first subheader. if subh.digest != Crypto.sha1(header1): raise ContentError("Invalid digest") # Get ready to generate packet keys. keys = Crypto.Keyset(subh.secret) # Replay prevention replayhash = keys.get(Crypto.REPLAY_PREVENTION_MODE, Crypto.DIGEST_LEN) if hashlog.seenHash(replayhash): raise ContentError("Duplicate packet detected.") else: hashlog.logHash(replayhash) # If we're meant to drop, drop now. rt = subh.routingtype if rt == Packet.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)) # Prepare key to generate padding junk_key = Crypto.aes_key(keys.get(Crypto.RANDOM_JUNK_MODE)) # Pad the rest of header 1 header1 += Crypto.prng(junk_key, Packet.OAEP_OVERHEAD + Packet.MIN_SUBHEADER_LEN + subh.routinglen) assert len(header1) == (Packet.HEADER_LEN - Packet.ENC_SUBHEADER_LEN + Packet.OAEP_OVERHEAD+Packet.MIN_SUBHEADER_LEN + subh.routinglen) assert len(header1) == 1792 + 42 + 42 + subh.routinglen == \ 1876 + subh.routinglen # Decrypt the rest of header 1, encrypting the padding. header1 = Crypto.ctr_crypt(header1, header_sec_key) # If the subheader says that we have extra routing info that didn't # fit in the RSA-encrypted part, get it now. overflowLength = subh.getOverflowLength() if overflowLength: subh.appendOverflow(header1[:overflowLength]) header1 = header1[overflowLength:] assert len(header1) == ( 1876 + subh.routinglen - max(0,subh.routinglen-Packet.MAX_ROUTING_INFO_LEN)) header1 = subh.underflow + header1 assert len(header1) == Packet.HEADER_LEN # Decrypt the payload. payload = Crypto.lioness_decrypt(pkt.payload, keys.getLionessKeys(Crypto.PAYLOAD_ENCRYPT_MODE)) # If we're an exit node, there's no need to process the headers # further. if rt >= Packet.MIN_EXIT_TYPE: return DeliveryPacket(rt, subh.getExitAddress(0), 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 (Packet.SWAP_FWD_IPV4_TYPE, Packet.FWD_IPV4_TYPE, Packet.SWAP_FWD_HOST_TYPE, Packet.FWD_HOST_TYPE): raise ContentError("Unrecognized Mixminion routing type") # Decrypt header 2. header2 = Crypto.lioness_decrypt(pkt.header2, keys.getLionessKeys(Crypto.HEADER_ENCRYPT_MODE)) # If we're the swap node, (1) decrypt the payload with a hash of # header2... (2) decrypt header2 with a hash of the payload... # (3) and swap the headers. if Packet.typeIsSwap(rt): hkey = Crypto.lioness_keys_from_header(header2) payload = Crypto.lioness_decrypt(payload, hkey) 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.parseRelayInfoByType(rt, subh.routinginfo) # Construct the packet for the next hop. pkt = Packet.Packet(header1, header2, payload).pack() return RelayedPacket(address, pkt)
def _buildHeader(path,secrets,exitType,exitInfo,paddingPRNG): """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 subheaders. exitType: The routing type for the last node in the header exitInfo: The routing info for the last node in the header. (Must include 20-byte decoding tag, if any.) paddingPRNG: A pseudo-random number generator to generate padding """ assert len(path) == len(secrets) for info in path: if not info.supportsPacketVersion(): raise MixError("Server %s does not support any recognized packet format."%info.getNickname()) routing, sizes, totalSize = _getRouting(path, exitType, exitInfo) if totalSize > HEADER_LEN: raise MixError("Path cannot 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) for secret in secrets ] # Length of padding needed for the header paddingLen = HEADER_LEN - totalSize # Calculate junk. # junkSeen[i]==the junk that node i will see, before it does any # encryption. Note that junkSeen[0]=="", because node 0 # sees no junk. junkSeen = [""] for secret, headerKey, size in zip(secrets, headerKeys, sizes): # Here we're calculating the junk that node i+1 will see. # # Node i+1 sees the junk that node i saw, plus the junk that i appends, # all encrypted by i. prngKey = Crypto.Keyset(secret).get(Crypto.RANDOM_JUNK_MODE) # newJunk is the junk that node i will append. (It's as long as # the data that i removes.) newJunk = Crypto.prng(prngKey,size) lastJunk = junkSeen[-1] nextJunk = lastJunk + newJunk # Before we encrypt the junk, we'll encrypt all the data, and # all the initial padding, but not the RSA-encrypted part. # This is equal to - 256 # + sum(size[current]....size[last]) # + paddingLen # This simplifies to: #startIdx = paddingLen - 256 + totalSize - len(lastJunk) startIdx = HEADER_LEN - ENC_SUBHEADER_LEN - len(lastJunk) nextJunk = Crypto.ctr_crypt(nextJunk, headerKey, startIdx) junkSeen.append(nextJunk) # We start with the padding. header = paddingPRNG.getBytes(paddingLen) # Now, we build the subheaders, iterating through the nodes backwards. 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], None, #placeholder for as-yet-uncalculated digest rt, ri) # Do we need to include some of the remaining header in the # RSA-encrypted portion? underflowLength = subhead.getUnderflowLength() if underflowLength > 0: underflow = header[:underflowLength] header = header[underflowLength:] else: underflow = "" # Do we need to spill some of the routing info out from the # RSA-encrypted portion? If so, prepend it. header = subhead.getOverflow() + header # Encrypt the symmetrically encrypted part of the header header = Crypto.ctr_crypt(header, headerKeys[i]) # What digest will the next server see? subhead.digest = Crypto.sha1(header+junkSeen[i]) # Encrypt the subheader, plus whatever portion of the previous header # underflows, into 'esh'. pubkey = path[i].getPacketKey() rsaPart = subhead.pack() + underflow esh = Crypto.pk_encrypt(rsaPart, pubkey) # Concatenate the asymmetric and symmetric parts, to get the next # header. header = esh + header return header
def _buildHeader(path, secrets, exitType, exitInfo, paddingPRNG): """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 subheaders. exitType: The routing type for the last node in the header exitInfo: The routing info for the last node in the header. (Must include 20-byte decoding tag, if any.) paddingPRNG: A pseudo-random number generator to generate padding """ assert len(path) == len(secrets) for info in path: if not info.supportsPacketVersion(): raise MixError("Server %s does not support any recognized packet format." % info.getNickname()) routing, sizes, totalSize = _getRouting(path, exitType, exitInfo) if totalSize > HEADER_LEN: raise MixError("Path cannot 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) for secret in secrets] # Length of padding needed for the header paddingLen = HEADER_LEN - totalSize # Calculate junk. # junkSeen[i]==the junk that node i will see, before it does any # encryption. Note that junkSeen[0]=="", because node 0 # sees no junk. junkSeen = [""] for secret, headerKey, size in zip(secrets, headerKeys, sizes): # Here we're calculating the junk that node i+1 will see. # # Node i+1 sees the junk that node i saw, plus the junk that i appends, # all encrypted by i. prngKey = Crypto.Keyset(secret).get(Crypto.RANDOM_JUNK_MODE) # newJunk is the junk that node i will append. (It's as long as # the data that i removes.) newJunk = Crypto.prng(prngKey, size) lastJunk = junkSeen[-1] nextJunk = lastJunk + newJunk # Before we encrypt the junk, we'll encrypt all the data, and # all the initial padding, but not the RSA-encrypted part. # This is equal to - 256 # + sum(size[current]....size[last]) # + paddingLen # This simplifies to: # startIdx = paddingLen - 256 + totalSize - len(lastJunk) startIdx = HEADER_LEN - ENC_SUBHEADER_LEN - len(lastJunk) nextJunk = Crypto.ctr_crypt(nextJunk, headerKey, startIdx) junkSeen.append(nextJunk) # We start with the padding. header = paddingPRNG.getBytes(paddingLen) # Now, we build the subheaders, iterating through the nodes backwards. 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], None, rt, ri) # placeholder for as-yet-uncalculated digest # Do we need to include some of the remaining header in the # RSA-encrypted portion? underflowLength = subhead.getUnderflowLength() if underflowLength > 0: underflow = header[:underflowLength] header = header[underflowLength:] else: underflow = "" # Do we need to spill some of the routing info out from the # RSA-encrypted portion? If so, prepend it. header = subhead.getOverflow() + header # Encrypt the symmetrically encrypted part of the header header = Crypto.ctr_crypt(header, headerKeys[i]) # What digest will the next server see? subhead.digest = Crypto.sha1(header + junkSeen[i]) # Encrypt the subheader, plus whatever portion of the previous header # underflows, into 'esh'. pubkey = path[i].getPacketKey() rsaPart = subhead.pack() + underflow esh = Crypto.pk_encrypt(rsaPart, pubkey) # Concatenate the asymmetric and symmetric parts, to get the next # header. header = esh + header return header