def verifySignature(self, rsa_obj, data, sig, broadcast): # 0:2 = kind # 2:8 = neighbor ipp # 8:9 = hop limit # 9:10 = flags # 10:16 = source ipp # 16: = "the rest" if not rsa_obj: return False # Grab the kind, skip over the header, and get everything up # to but not including the signature. if broadcast: body = data[0:2] + data[10:-len(sig)] else: body = data[:-len(sig)] data_hash = md5(body).digest() sig_tuple = (bytes_to_long(sig),) try: return rsa_obj.verify(data_hash, sig_tuple) except: return False
def verifySignature(self, rsa_obj, data, sig, broadcast): # 0:2 = kind # 2:8 = neighbor ipp # 8:9 = hop limit # 9:10 = flags # 10:16 = source ipp # 16: = "the rest" if not rsa_obj: return False # Grab the kind, skip over the header, and get everything up # to but not including the signature. if broadcast: body = data[0:2] + data[10:-len(sig)] else: body = data[:-len(sig)] data_hash = md5(body).digest() sig_tuple = (bytes_to_long(sig), ) try: return rsa_obj.verify(data_hash, sig_tuple) except: return False
def check_cb(src_n, src_ipp, rest): (pktnum, expire, sesid, uptime, flags, rest) = self.decodePacket('!QH4sIB+', rest) persist = bool(flags & core.PERSIST_BIT) (hashes, rest) = self.decodeString1(rest, 16) hashes = [h for h, in self.decodeChunkList('!16s', hashes)] (pubkey, signature) = self.decodeString2(rest) if not (expire <= 30 * 60): raise BadPacketError("Expire time out of range") # If the signed message is too old, discard it. if osm.bcm.signatureExpired(pktnum): raise BadBroadcast # If we've received a newer status update, then this is useless. if self.isOutdatedBridgeStatus(src_n, pktnum): raise BadBroadcast # Make sure public key matches a hash in DNS pkhash = md5(pubkey).digest() if pkhash not in self.main.state.dns_pkhashes: # Not useful to me, but still forward it return # Generate RSA object from public key try: rsa_obj = RSA.construct((bytes_to_long(pubkey), 65537L)) except: return # Verify signature if not self.verifySignature( rsa_obj, data, signature, broadcast=True): # Useless... raise BadBroadcast # Keep track of the timestamp osm.bcm.updateBridgeTime(pktnum) # Update basic status n = osm.refreshNodeStatus(src_ipp, None, expire, sesid, uptime, persist, '', '') # Update bridge-specific status osm.bcm.refreshBridgeNodeStatus(n, pktnum, rsa_obj, hashes, do_request=False)
def check_cb(src_n, src_ipp, rest): (pktnum, expire, sesid, uptime, flags, rest ) = self.decodePacket('!QH4sIB+', rest) persist = bool(flags & core.PERSIST_BIT) (hashes, rest ) = self.decodeString1(rest, 16) hashes = [h for h, in self.decodeChunkList('!16s', hashes)] (pubkey, signature ) = self.decodeString2(rest) if not (expire <= 30*60): raise BadPacketError("Expire time out of range") # If the signed message is too old, discard it. if osm.bcm.signatureExpired(pktnum): raise BadBroadcast # If we've received a newer status update, then this is useless. if self.isOutdatedBridgeStatus(src_n, pktnum): raise BadBroadcast # Make sure public key matches a hash in DNS pkhash = md5(pubkey).digest() if pkhash not in self.main.state.dns_pkhashes: # Not useful to me, but still forward it return # Generate RSA object from public key try: rsa_obj = RSA.construct((bytes_to_long(pubkey), 65537L)) except: return # Verify signature if not self.verifySignature( rsa_obj, data, signature, broadcast=True): # Useless... raise BadBroadcast # Keep track of the timestamp osm.bcm.updateBridgeTime(pktnum) # Update basic status n = osm.refreshNodeStatus( src_ipp, None, expire, sesid, uptime, persist, '', '') # Update bridge-specific status osm.bcm.refreshBridgeNodeStatus( n, pktnum, rsa_obj, hashes, do_request=False)
def encrypt(self, data): # AES requires packets in blocks of 16 bytes so pad the packet with # its MD5 hash. We need a minimum of 5 hash bytes. # There's also 1 byte at the end to store the hash length hlen = ((10 - len(data)) % 16) + 5 h = md5(data).digest()[:hlen] + '\0' * (hlen - 16) data += h + chr(hlen) return self.aes.encrypt(data)
def encrypt(self, data): # AES requires packets in blocks of 16 bytes so pad the packet with # its MD5 hash. We need a minimum of 5 hash bytes. # There's also 1 byte at the end to store the hash length hlen = ((10-len(data)) % 16) + 5 h = md5(data).digest()[:hlen] + '\0'*(hlen-16) data += h + chr(hlen) return self.aes.encrypt(data)
def signPacket(self, packet, broadcast): data = ''.join(packet) if broadcast: body = data[0:2] + data[10:] else: body = data data_hash = md5(body).digest() t = time.time() sig, = self.rsa_obj.sign(data_hash, None) LOG.debug("Sign Time = %f sec" % (time.time() - t)) packet.append(long_to_bytes(sig))
def handleDataBlock(self, ipp, data): # Call this when a data block arrives from the network osm = self.main.osm bhash = md5(data).digest() key = (ipp, bhash) try: bdata = osm.lookup_ipp[ipp].bridge_data if not bdata: raise KeyError except KeyError: self.addUnclaimedDataBlock(key, data) else: if not bdata.addDataBlock(bhash, data): self.addUnclaimedDataBlock(key, data)
def decrypt(self, data): if not (data and len(data) % 16 == 0): raise ValueError("Bad Length") data = self.aes.decrypt(data) hlen = ord(data[-1]) if not (5 <= hlen < len(data)): raise ValueError("Bad Hash Length") h = data[-(hlen+1):-1][:16] data = data[:-(hlen+1)] if h != md5(data).digest()[:hlen]: raise ValueError("Bad Hash Value") return data
def decrypt(self, data): if not (data and len(data) % 16 == 0): raise ValueError("Bad Length") data = self.aes.decrypt(data) hlen = ord(data[-1]) if not (5 <= hlen < len(data)): raise ValueError("Bad Hash Length") h = data[-(hlen + 1):-1][:16] data = data[:-(hlen + 1)] if h != md5(data).digest()[:hlen]: raise ValueError("Bad Hash Value") return data
def __init__(self, key): self.aes = AES.new(md5(key).digest())
def handlePacket_bY(self, ad, data): # Bridge Sync Reply osm = self.main.osm if not osm: raise BadTimingError("Not ready for bridge sync reply") (kind, src_ipp, pktnum, expire, sesid, uptime, flags, rest ) = self.decodePacket('!2s6sQH4sIB+', data) self.checkSource(src_ipp, ad, exempt_ip=True) persist = bool(flags & core.PERSIST_BIT) (hashes, rest ) = self.decodeString1(rest, 16) hashes = [h for h, in self.decodeChunkList('!16s', hashes)] (pubkey, rest ) = self.decodeString2(rest) c_nbs, rest = self.decodeNodeList(rest) u_nbs, rest = self.decodeNodeList(rest) signature = rest if not (expire <= 30*60): raise BadPacketError("Expire time out of range") class Skip(Exception): pass try: # If the signed message is too old, discard it. if osm.bcm.signatureExpired(pktnum): raise Skip # If we've received a newer status update, then this is useless. try: n = osm.lookup_ipp[src_ipp] except KeyError: pass else: if self.isOutdatedBridgeStatus(n, pktnum): raise Skip # Make sure public key matches a hash in DNS pkhash = md5(pubkey).digest() if pkhash not in self.main.state.dns_pkhashes: raise Skip # Generate RSA object from public key try: rsa_obj = RSA.construct((bytes_to_long(pubkey), 65537L)) except: raise Skip # Verify signature if not self.verifySignature( rsa_obj, data, signature, broadcast=False): raise Skip # Keep track of the timestamp osm.bcm.updateBridgeTime(pktnum) # Update basic status n = osm.refreshNodeStatus( src_ipp, None, expire, sesid, uptime, persist, '', '') # Update bridge-specific status osm.bcm.refreshBridgeNodeStatus( n, pktnum, rsa_obj, hashes, do_request=True) except Skip: pass # Process the sync reply if osm.sm: osm.sm.receivedSyncReply(src_ipp, c_nbs, u_nbs)
def handlePacket_bY(self, ad, data): # Bridge Sync Reply osm = self.main.osm if not osm: raise BadTimingError("Not ready for bridge sync reply") (kind, src_ipp, pktnum, expire, sesid, uptime, flags, rest) = self.decodePacket('!2s6sQH4sIB+', data) self.checkSource(src_ipp, ad, exempt_ip=True) persist = bool(flags & core.PERSIST_BIT) (hashes, rest) = self.decodeString1(rest, 16) hashes = [h for h, in self.decodeChunkList('!16s', hashes)] (pubkey, rest) = self.decodeString2(rest) c_nbs, rest = self.decodeNodeList(rest) u_nbs, rest = self.decodeNodeList(rest) signature = rest if not (expire <= 30 * 60): raise BadPacketError("Expire time out of range") class Skip(Exception): pass try: # If the signed message is too old, discard it. if osm.bcm.signatureExpired(pktnum): raise Skip # If we've received a newer status update, then this is useless. try: n = osm.lookup_ipp[src_ipp] except KeyError: pass else: if self.isOutdatedBridgeStatus(n, pktnum): raise Skip # Make sure public key matches a hash in DNS pkhash = md5(pubkey).digest() if pkhash not in self.main.state.dns_pkhashes: raise Skip # Generate RSA object from public key try: rsa_obj = RSA.construct((bytes_to_long(pubkey), 65537L)) except: raise Skip # Verify signature if not self.verifySignature( rsa_obj, data, signature, broadcast=False): raise Skip # Keep track of the timestamp osm.bcm.updateBridgeTime(pktnum) # Update basic status n = osm.refreshNodeStatus(src_ipp, None, expire, sesid, uptime, persist, '', '') # Update bridge-specific status osm.bcm.refreshBridgeNodeStatus(n, pktnum, rsa_obj, hashes, do_request=True) except Skip: pass # Process the sync reply if osm.sm: osm.sm.receivedSyncReply(src_ipp, c_nbs, u_nbs)
def md5(self, in_str): return md5(in_str).digest()
def getEntries(self): # Build and return a dict of entries which should be sent to the # dynamic config store. def b64(arg): return binascii.b2a_base64(arg).rstrip() # Dictionary of key=value pairs to return. # Start out with the static entries provided in the config. entries = cfg.dconfig_fixed_entries.copy() osm = self.main.osm # Generate public key hash if cfg.private_key: pubkey = long_to_bytes(RSA.construct(cfg.private_key).n) entries['pkhash'] = b64(md5(pubkey).digest()) # Collect IPPs for the ipcache string GOAL = 10 ipps = set() # Initially add all the exempt IPs, without a port for ip in self.main.state.exempt_ips: ad = Ad() ad.ip = ip ad.port = 0 ipps.add(ad.getRawIPPort()) # Helper function to add an IPP, overriding any portless entries. def add_ipp(ipp): ipps.discard(ipp[:4] + '\0\0') ipps.add(ipp) syncd = (osm and osm.syncd) # Add my own IP. # If I'm a hidden node, then only add it if I'm not online yet. if (not self.main.hide_node) or (not syncd): if osm: add_ipp(osm.me.ipp) else: try: add_ipp(self.main.selectMyIP()) except ValueError: pass # Add the IPPs of online nodes if syncd: sec = seconds() def n_uptime(n): uptime = max(0, sec - n.uptime) if n.persist: uptime *= 1.5 return -uptime # Sort nodes by uptime, highest first nodes = osm.nodes[:] nodes.sort(key=n_uptime) # Chop list down to the top eighth or so. del nodes[min(GOAL, len(nodes) // 8):] # Select a random sample from the best nodes. try: nodes = random.sample(nodes, GOAL) except ValueError: pass for n in nodes: add_ipp(n.ipp) if len(ipps) >= GOAL: break # Add the IPPs of offline nodes (if necessary) if len(ipps) < GOAL: for when,ipp in self.main.state.getYoungestPeers(GOAL): add_ipp(ipp) if len(ipps) >= GOAL: break ipcache = list(ipps) random.shuffle(ipcache) ipcache = '\xFF\xFF\xFF\xFF' + ''.join(ipcache) ipcache = b64(self.main.pk_enc.encrypt(ipcache)) entries['ipcache'] = ipcache return entries
def n_user(ipp): h = binascii.hexlify(md5(ipp).digest())[:6] return "dt" + h.upper()
def getStateData(self, packet): # All the state info common between BS and Br packets osm = self.main.osm CHECK(osm and osm.syncd) # Get the IRC Server, if it's ready ism = self.main.ism # Sequence number packet.append(self.nextPktNum()) # Expiration time when = int(dcall_timeleft(self.sendState_dcall)) packet.append(struct.pack("!H", when)) # Session ID, uptime flags packet.append(osm.me.sesid) packet.append(struct.pack("!I", int(seconds() - osm.me.uptime))) packet.append(struct.pack("!B", core.PERSIST_BIT)) chunks = [] # Add info strings self.addInfoChunk(chunks) if ism: # Add the list of online nicks for dnick, infoindex in ism.getNicksAndInfo(): self.addNickChunk(chunks, dnick, infoindex) self.addTopicChunk( chunks, ism.topic_whoset, ism.topic, changed=False) # Get bans list for ip, mask in ism.bans: self.addBanChunk(chunks, ip, mask, True) if ism.moderated: self.addModeratedChunk(chunks, True) chunks = ''.join(chunks) # Split data string into 1k blocks blocks = [] for i in range(0, len(chunks), 1024): blocks.append(chunks[i:i+1024]) block_hashes = [md5(b).digest() for b in blocks] # Add the list of block hashes packet.append(struct.pack("!B", len(block_hashes))) packet.extend(block_hashes) # Add the public key pubkey = long_to_bytes(self.rsa_obj.n) packet.append(struct.pack("!H", len(pubkey))) packet.append(pubkey) # Return hashes and blocks return block_hashes, blocks
def getStateData(self, packet): # All the state info common between BS and Br packets osm = self.main.osm CHECK(osm and osm.syncd) # Get the IRC Server, if it's ready ism = self.main.ism # Sequence number packet.append(self.nextPktNum()) # Expiration time when = int(dcall_timeleft(self.sendState_dcall)) packet.append(struct.pack("!H", when)) # Session ID, uptime flags packet.append(osm.me.sesid) packet.append(struct.pack("!I", int(seconds() - osm.me.uptime))) packet.append(struct.pack("!B", core.PERSIST_BIT)) chunks = [] # Add info strings self.addInfoChunk(chunks) if ism: # Add the list of online nicks for dnick, infoindex in ism.getNicksAndInfo(): self.addNickChunk(chunks, dnick, infoindex) self.addTopicChunk(chunks, ism.topic_whoset, ism.topic, changed=False) # Get bans list for ip, mask in ism.bans: self.addBanChunk(chunks, ip, mask, True) if ism.moderated: self.addModeratedChunk(chunks, True) chunks = ''.join(chunks) # Split data string into 1k blocks blocks = [] for i in range(0, len(chunks), 1024): blocks.append(chunks[i:i + 1024]) block_hashes = [md5(b).digest() for b in blocks] # Add the list of block hashes packet.append(struct.pack("!B", len(block_hashes))) packet.extend(block_hashes) # Add the public key pubkey = long_to_bytes(self.rsa_obj.n) packet.append(struct.pack("!H", len(pubkey))) packet.append(pubkey) # Return hashes and blocks return block_hashes, blocks
def getEntries(self): # Build and return a dict of entries which should be sent to the # dynamic config store. def b64(arg): return binascii.b2a_base64(arg).rstrip() # Dictionary of key=value pairs to return. # Start out with the static entries provided in the config. entries = cfg.dconfig_fixed_entries.copy() osm = self.main.osm # Generate public key hash if cfg.private_key: pubkey = long_to_bytes(RSA.construct(cfg.private_key).n) entries['pkhash'] = b64(md5(pubkey).digest()) # Collect IPPs for the ipcache string GOAL = 10 ipps = set() # Initially add all the exempt IPs, without a port for ip in self.main.state.exempt_ips: ad = Ad() ad.ip = ip ad.port = 0 ipps.add(ad.getRawIPPort()) # Helper function to add an IPP, overriding any portless entries. def add_ipp(ipp): ipps.discard(ipp[:4] + '\0\0') ipps.add(ipp) syncd = (osm and osm.syncd) # Add my own IP. # If I'm a hidden node, then only add it if I'm not online yet. if (not self.main.hide_node) or (not syncd): if osm: add_ipp(osm.me.ipp) else: try: add_ipp(self.main.selectMyIP().getRawIPPort()) except ValueError: pass # Add the IPPs of online nodes if syncd: sec = seconds() def n_uptime(n): uptime = max(0, sec - n.uptime) if n.persist: uptime *= 1.5 return -uptime # Sort nodes by uptime, highest first nodes = osm.nodes[:] nodes.sort(key=n_uptime) # Chop list down to the top eighth or so. del nodes[min(GOAL, len(nodes) // 8):] # Select a random sample from the best nodes. try: nodes = random.sample(nodes, GOAL) except ValueError: pass for n in nodes: add_ipp(n.ipp) if len(ipps) >= GOAL: break # Add the IPPs of offline nodes (if necessary) if len(ipps) < GOAL: for when,ipp in self.main.state.getYoungestPeers(GOAL): add_ipp(ipp) if len(ipps) >= GOAL: break ipcache = list(ipps) random.shuffle(ipcache) ipcache = '\xFF\xFF\xFF\xFF' + ''.join(ipcache) ipcache = b64(self.main.pk_enc.encrypt(ipcache)) entries['ipcache'] = ipcache return entries