def pool_maintenance(): """cleansen the pool: - remove addresses scheduled to be removed (in the in-use address) - regenerate the cache""" pool = EphemeralPool() with pool.TBremovedAddrLock: for (address, ttl) in pool.TBremovedAddr.items(): if ttl <= 0: # suppress the address from the system warn("scheduled removal for address %s is now removed\n" % address) address.remove() pool.nc.del_address(str(address)) del pool.TBremovedAddr[address] else: pool.TBremovedAddr[address] = ttl -1 # regenerate the pool of addresses remaining_addresses = 0 with pool.freePoolLock: remaining_addresses = len(pool.freePool) if remaining_addresses <= EphemeralAddress.POOL_SIZE/2 : pool.regenerate(EphemeralAddress.POOL_SIZE - remaining_addresses)
def pool_maintenance(): """cleansen the pool: - remove addresses scheduled to be removed (in the in-use address) - regenerate the cache""" pool = EphemeralPool() with pool.TBremovedAddrLock: for (address, ttl) in pool.TBremovedAddr.items(): if ttl <= 0: # suppress the address from the system warn("scheduled removal for address %s is now removed\n" % address) address.remove() pool.nc.del_address(str(address)) del pool.TBremovedAddr[address] else: pool.TBremovedAddr[address] = ttl - 1 # regenerate the pool of addresses remaining_addresses = 0 with pool.freePoolLock: remaining_addresses = len(pool.freePool) if remaining_addresses <= EphemeralAddress.POOL_SIZE / 2: pool.regenerate(EphemeralAddress.POOL_SIZE - remaining_addresses)
def run_queues(): """run the different queues and dispatch packets for the different callback functions""" in_q = in_queue() out_q = out_queue() cpscpa_q = cpscpa_queue() plugins = get_plugins_by_capability("NFQueue") plugin_queues = [ plugin.listening_queue() \ for plugin in plugins ] # associate a file descritor to its corresponding queue queues = {} for q in [in_q, out_q, cpscpa_q] + plugin_queues: q.set_mode(nfqueue.NFQNL_COPY_PACKET) queues[q.get_fd()] = q warn("running In/Out/CPSCPA NFQueues\n") # TC 04/15/10: took me a while to figure out I could use nfqueues this way try: while True: # warn("waiting for new (intercepted) messages\n") (r, w, o) = select(queues.keys(), [], []) for filedesc in r: queues[filedesc].process_pending(1) except KeyboardInterrupt, e: print "stopping all the queues"
def reportaddress_as_used(self, address): """report the address as an address that is currently in use""" # connect to the NC to obtain the list of currently assigned addresses configured_addresses = self.nc.dump_addresses() # address = None # obtain the address object for address_obj in (a for a in configured_addresses if str(a) == address): address = address_obj break # we can stop here as the address is only recorded one # change the state of the address to deprecated try: address.modify(preferred_lft=0) warn( "Address %s reported to have initialized a connexion is now being deprecated\n" % address) except AttributeError: # the address does not belong to our program pass # if this is an ephemeral CGA from the "free" pool, place it # in the "in use" pool try: with nested(self.freePoolLock, self.inUsePoolLock): index = self.freePool.index(address) del self.freePool[index] self.inUsePool.append(address) except (ValueError, IndexError): pass
def reportaddress_as_used(self, address): """report the address as an address that is currently in use""" # connect to the NC to obtain the list of currently assigned addresses configured_addresses = self.nc.dump_addresses() # address = None # obtain the address object for address_obj in ( a for a in configured_addresses if str(a) == address): address = address_obj break # we can stop here as the address is only recorded one # change the state of the address to deprecated try: address.modify(preferred_lft=0) warn("Address %s reported to have initialized a connexion is now being deprecated\n" % address) except AttributeError: # the address does not belong to our program pass # if this is an ephemeral CGA from the "free" pool, place it # in the "in use" pool try: with nested(self.freePoolLock, self.inUsePoolLock): index = self.freePool.index(address) del self.freePool[index] self.inUsePool.append(address) except (ValueError, IndexError): pass
def run_queues(): """run the different queues and dispatch packets for the different callback functions""" in_q = in_queue() out_q = out_queue() cpscpa_q = cpscpa_queue() plugins = get_plugins_by_capability("NFQueue") plugin_queues = [ plugin.listening_queue() \ for plugin in plugins ] # associate a file descritor to its corresponding queue queues = {} for q in [in_q, out_q, cpscpa_q] + plugin_queues: q.set_mode(nfqueue.NFQNL_COPY_PACKET) queues[q.get_fd()] = q warn("running In/Out/CPSCPA NFQueues\n") # TC 04/15/10: took me a while to figure out I could use nfqueues this way try: while True: # warn("waiting for new (intercepted) messages\n") (r,w,o) = select(queues.keys(),[],[]) for filedesc in r: queues[filedesc].process_pending(1) except KeyboardInterrupt, e: print "stopping all the queues"
def storecert(self,id,cert): """temporarly store certs, they are sorted by their ID""" warn("storing on cert for Certificate Path #%d\n" % id) with self.TBApprLock: certpath = [] ttl = TIMEOUT try: (certpath, oldttl) = self.TBApprovedCert[id] except KeyError: pass certpath.append(Cert(cert)) self.TBApprovedCert[id] = (certpath, ttl)
def readconfig(config_file): """read the config file, check the different option, and return them to the main program""" NDprotector.ECCsupport = False try: from scapy6send.ecc import ECCkey, NID_secp256k1,\ NID_secp384r1, NID_secp521r1 except ImportError: warn("unable to import ECC library\n") warn("ECC support is disabled\n") else: warn("ECC support is available\n") NDprotector.ECCsupport = True # load the config file execfile(config_file) # TODO # - sanity checks # load the various plugins init_plugin_path(NDprotector.pluginpath) load_plugins(NDprotector.plugins) warn("available plugins: " + repr(find_plugins()) + "\n")
def storecert(self, id, cert): """temporarly store certs, they are sorted by their ID""" warn("storing on cert for Certificate Path #%d\n" % id) with self.TBApprLock: certpath = [] ttl = TIMEOUT try: (certpath, oldttl) = self.TBApprovedCert[id] except KeyError: pass certpath.append(Cert(cert)) self.TBApprovedCert[id] = (certpath, ttl)
def SendRTSol(): """send a simple Router Solicitation message on all the configured interfaces""" # get all the interfaces on # which we should send a message on nc = NeighCache() configured_addresses = nc.dump_addresses() interfaces = used_interfaces(configured_addresses) for iface in interfaces: p = Ether(src=get_if_hwaddr(iface)) / \ IPv6(src = "::",dst = "ff02::2")/ \ ICMPv6ND_RS() sendp(p,iface=iface,verbose=NDprotector.verbose) warn("Sending an RS on interface %s\n" % iface)
def SendRTSol(): """send a simple Router Solicitation message on all the configured interfaces""" # get all the interfaces on # which we should send a message on nc = NeighCache() configured_addresses = nc.dump_addresses() interfaces = used_interfaces(configured_addresses) for iface in interfaces: p = Ether(src=get_if_hwaddr(iface)) / \ IPv6(src = "::",dst = "ff02::2")/ \ ICMPv6ND_RS() sendp(p, iface=iface, verbose=NDprotector.verbose) warn("Sending an RS on interface %s\n" % iface)
def trustable_hash(self, hash_cert, prefixes, sigtypeID): """check if a hash contained in a RSA signature option corresponds to a trustable certificate Also check if the certificate's IP Addr extension matches to the advertised prefixes""" hashfunc = getattr(hashlib, SigTypeHashfunc[sigtypeID]) try: with self.ApprLock: # case #1: we have accepted a certificate for these prefixes for cert in self.ApprovedCert: if NDprotector.x509_ipextension: (addr, preflen) = cert.IPAddrExt addr = addr_to_int(addr) if hash_cert == hashfunc(cert.key.derkey).digest()[:16]: if NDprotector.x509_ipextension: for prefix in\ (((addr_to_int(p) & prefix_mask(preflen)) for p in prefixes)): if prefix != (addr & prefix_mask(preflen)): return False else: return True elif not NDprotector.x509_ipextension: return True # case #2: the certificate linked to the messages # is directly a trust anchor for certfile in NDprotector.trustanchors: cert = Cert(certfile) if NDprotector.x509_ipextension: (addr, preflen) = cert.IPAddrExt addr = addr_to_int(addr) if hash_cert == sha.sha(cert.key.derkey).digest()[:16]: if NDprotector.x509_ipextension: for prefix in ((addr_to_int(p) & prefix_mask(preflen) for p in prefixes)): if prefix != (addr & prefix_mask(preflen)): return False else: return True elif not NDprotector.x509_ipextension: return True # likely due to a missing IP Addr extension except TypeError: warn("The verified certificate most likely " "does not have any IP addresses extension field\n") return False
def NeighborCacheStart(): # start the cleaning Thread for the NC NDprotector.Cleanup.cleanup_thread_subscribe(cleaning_nc) # register the currently assigned addresses nc = NeighCache() for addr in NDprotector.configured_addresses: nc.store_address(addr) #flushing all the old data from the kernel NC warn("flushing kernel neighbor cache\n") p = Popen( [ '/sbin/ip', '-6', 'neigh', 'flush', 'all' ], \ stdout=PIPE, stderr=PIPE) if p.stdout.read() + p.stderr.read() != "": # raise an error raise NeighCacheException("unable to flush the kernel neighbor cache")
def trustable_hash(self,hash_cert, prefixes, sigtypeID): """check if a hash contained in a RSA signature option corresponds to a trustable certificate Also check if the certificate's IP Addr extension matches to the advertised prefixes""" hashfunc = getattr(hashlib, SigTypeHashfunc[sigtypeID]) try: with self.ApprLock: # case #1: we have accepted a certificate for these prefixes for cert in self.ApprovedCert: if NDprotector.x509_ipextension: (addr,preflen) = cert.IPAddrExt addr = addr_to_int(addr) if hash_cert == hashfunc(cert.key.derkey).digest()[:16]: if NDprotector.x509_ipextension: for prefix in\ (((addr_to_int(p) & prefix_mask(preflen)) for p in prefixes)): if prefix != (addr & prefix_mask(preflen)): return False else: return True elif not NDprotector.x509_ipextension: return True # case #2: the certificate linked to the messages # is directly a trust anchor for certfile in NDprotector.trustanchors: cert = Cert(certfile) if NDprotector.x509_ipextension: (addr,preflen) = cert.IPAddrExt addr = addr_to_int(addr) if hash_cert == sha.sha(cert.key.derkey).digest()[:16]: if NDprotector.x509_ipextension: for prefix in ((addr_to_int(p) & prefix_mask(preflen) for p in prefixes)): if prefix != (addr & prefix_mask(preflen)): return False else: return True elif not NDprotector.x509_ipextension: return True # likely due to a missing IP Addr extension except TypeError: warn("The verified certificate most likely " "does not have any IP addresses extension field\n") return False
def regenerate(self, n_address): """create n_address addresses to fill the pool there should be POOL_SIZE addresses in the pool""" warn("Ephemeral CGA pool: regeneration of %i addresses\n" % n_address) for i in range(n_address): a = Address(key=NDprotector.default_publickey, interface=EphemeralAddress.INTERFACE, prefix=EphemeralAddress.PREFIX, ephemeral=True, sec=0, dad=False) # temporary a.modify(preferred_lft=0) with self.freePoolLock: self.freePool.append(a) # connect to the NC to store the new address self.nc.store_address(a) warn("Ephemeral CGA pool: regeneration of %i addresses complete\n" % n_address)
def regenerate(self, n_address): """create n_address addresses to fill the pool there should be POOL_SIZE addresses in the pool""" warn("Ephemeral CGA pool: regeneration of %i addresses\n" % n_address) for i in range(n_address): a = Address(key=NDprotector.default_publickey, interface = EphemeralAddress.INTERFACE, prefix = EphemeralAddress.PREFIX, ephemeral = True, sec = 0, dad=False) # temporary a.modify(preferred_lft=0) with self.freePoolLock: self.freePool.append(a) # connect to the NC to store the new address self.nc.store_address(a) warn("Ephemeral CGA pool: regeneration of %i addresses complete\n" % n_address)
def callback(i, payload): """a callback function for the NFQUEUE""" data = payload.get_data() data = IPv6(data) CGApool = EphemeralPool() # we might have intercepted packet for addresses # that are NOT Ephemeral CGA # if this is a SYN packet: # - put back the address in deprecated mode # - preparre the next address for the preferred state # a TCP SYN only if data[TCP].flags == 2: warn("this is a TCP SYN (address %s goes to deprecated state)\n" % data.src) CGApool.reportaddress_as_used(data.src) if CGApool.is_ephemeral_CGA(data.src): CGApool.prepare_next_valid_address() # first idea: if this is a FIN + ACK (17) packet, remove the address for the interface # second idea: if there is one FIN on each side, wait a bit and remove the address # (we implemen the second idea) elif data[TCP].flags & 1: warn("this is a TCP FIN (addresses %s-%s)\n" % (data.src, data.dst)) if EphemeralAddress.connexion_closed(data.src, data.dst): if CGApool.is_ephemeral_CGA(data.src): warn("address %s scheduled to be removed from the interface\n"\ % data.src) CGApool.schedule_address_removal( data.src ) elif CGApool.is_ephemeral_CGA(data.dst): warn("address %s scheduled to be removed from the interface\n"\ % data.src) CGApool.schedule_address_removal( data.dst ) payload.set_verdict(nfqueue.NF_ACCEPT) return 1
def callback(i, payload): """a callback function for the NFQUEUE""" data = payload.get_data() data = IPv6(data) CGApool = EphemeralPool() # we might have intercepted packet for addresses # that are NOT Ephemeral CGA # if this is a SYN packet: # - put back the address in deprecated mode # - preparre the next address for the preferred state # a TCP SYN only if data[TCP].flags == 2: warn("this is a TCP SYN (address %s goes to deprecated state)\n" % data.src) CGApool.reportaddress_as_used(data.src) if CGApool.is_ephemeral_CGA(data.src): CGApool.prepare_next_valid_address() # first idea: if this is a FIN + ACK (17) packet, remove the address for the interface # second idea: if there is one FIN on each side, wait a bit and remove the address # (we implemen the second idea) elif data[TCP].flags & 1: warn("this is a TCP FIN (addresses %s-%s)\n" % (data.src, data.dst)) if EphemeralAddress.connexion_closed(data.src, data.dst): if CGApool.is_ephemeral_CGA(data.src): warn("address %s scheduled to be removed from the interface\n"\ % data.src) CGApool.schedule_address_removal(data.src) elif CGApool.is_ephemeral_CGA(data.dst): warn("address %s scheduled to be removed from the interface\n"\ % data.src) CGApool.schedule_address_removal(data.dst) payload.set_verdict(nfqueue.NF_ACCEPT) return 1
def update(self, address, mac_address, pubkey, timestamp, secured = False, ssao = ([], []) ): """update an entry if the new record is more secure or up-to-date than the old one""" TSnew = timestamp # local reception time of the packet RDnew = ICMPv6NDOptTimestamp(str(ICMPv6NDOptTimestamp())).timestamp # clocks must be loosely synchronized if TSnew and fabs( RDnew - TSnew) >= NDprotector.ts_delta: warn("current message's Timestamp is out of sync\n") return False # an unsecured entry can not be recorded when using strict mode if not secured and NDprotector.mixed_mode == False: return False with self.lock: try: old_params = self.nd[address] (_, _, TSlast, oldsecured, oldssao, RDlast, _) = old_params if oldsecured and not secured: # an unsecured entry will not overwrite a secure entry warn("Trying to overwrite a secure NC entry with an unsecure entry\n") return False if TSnew and TSnew + NDprotector.ts_fuzz <= \ TSlast + (RDnew - RDlast) * (1 - NDprotector.ts_drift) - NDprotector.ts_fuzz: warn("current message has been replayed\n") return False except KeyError: pass self.nd[address] = tuple([ mac_address, pubkey, TSnew, secured, ssao, RDnew, TIMEOUT] ) return True
def callback(i, payload): """a callback function called on each ingoing packets""" data = payload.get_data() packet = IPv6(data) # we try to extract the SSAO if available try: (signalgs, verifalgs) = SigAlgList_split(packet[ICMPv6NDOptSSA].sigalgs) except AttributeError: signalgs = [] verifalgs = [] # may be buggy due to the order of get_if_list() # (in that case, we could use the if_nametoindex() ) interface = get_if_list()[payload.get_indev() - 1] nc = NeighCache() if packet.haslayer(ICMPv6ND_NS) \ or packet.haslayer(ICMPv6ND_NA): secured = False try: address = packet[IPv6].src # we can receive messages from other node performing a DAD if address == "::": address = packet.tgt cga_prop = CGAverify(address, packet.getlayer(CGAParams)) if not cga_prop: warn("an ingoing packet has failed CGA verification test\n") else: # only perform the RSA signature here, # Timestamp and Nonce verification are performed in the NC # FIXME: this is not "good", we are subject to a DoS attack here list_of_pubkeys = CGAPKExtListtoPubKeyList( packet[CGAParams].ext) list_of_pubkeys.insert(0, packet[CGAParams].pubkey) pubkey = list_of_pubkeys[packet[ICMPv6NDOptUSSig].pos] if isinstance(pubkey, PubKey) and \ not (NDprotector.min_RSA_key_size <= pubkey.modulusLen and pubkey.modulusLen <= NDprotector.max_RSA_key_size): warn("A message we received contains" " a Public Key whose size is not allowed\n") else: # Public Key is of a correct size if packet[ICMPv6NDOptUSSig].keyh !=\ get_public_key_hash(pubkey, packet[ICMPv6NDOptUSSig].sigtypeID) : warn("Public key contained in the CGA PDS does not match " \ "the Public Key hash in the Universal Sig option\n") else: if not packet[ICMPv6NDOptUSSig].verify_sig(pubkey): warn( "an ingoing packet has failed Universal Signature verification\n" ) else: secured = True except AttributeError: warn("An unsecured packet passed\n") # if the status is true, we allow the packet to pass status = False # we can extract the Source Link Layer option from a NS if packet.haslayer(ICMPv6ND_NS): mac_address = None try: mac_address = packet.getlayer(ICMPv6NDOptSrcLLAddr).lladdr except AttributeError: pass # the request does not contain # any information on L2 address if mac_address == None: status = True else: src_address = packet.getlayer(IPv6).src if secured: public_key = str(packet.getlayer(CGAParams).pubkey) timestamp = packet.getlayer(ICMPv6NDOptTimestamp).timestamp warn( "NC updating an entry with an secured NS message from the address: %s\n" % packet.src) status = nc.update(src_address, mac_address, public_key, timestamp, secured, ssao=(signalgs, verifalgs)) if not status: warn( "NS Message from %s rejected (due to a bad timestamp or nonce)\n" % packet.src) # record the nonce if any if packet.haslayer(ICMPv6NDOptNonce): nc.record_nonce_in(packet[IPv6].src, packet[IPv6].dst, packet[ICMPv6NDOptNonce].nonce) elif NDprotector.mixed_mode == True: warn( "NC updating an entry with an unsecured NS message from the address: %s\n" % packet.src) status = nc.update(src_address, mac_address, None, None, secured, ssao=(signalgs, verifalgs)) else: warn( "NC not updating an entry with an unsecured NS message from the address: %s\n" % packet.src) status = False # this is a NA, we extract the Target Link Layer option else: target = packet.tgt mac_address = None try: mac_address = packet.getlayer(ICMPv6NDOptDstLLAddr).lladdr except AttributeError: pass if mac_address == None: status = True else: if secured: public_key = str(packet.getlayer(CGAParams).pubkey) timestamp = packet.getlayer(ICMPv6NDOptTimestamp).timestamp try: nonce = packet.getlayer(ICMPv6NDOptNonce).nonce except AttributeError: nonce = None warn( "NC updating an entry with an secured NA message from the address: %s\n" % packet.src) # multicasted messages may not contain any nonce as they are sent spontaneously if packet[IPv6].dst != "ff02::1": status = nc.check_nonce_in(packet[IPv6].src, packet[IPv6].dst, nonce) # only update when the nonce is correct status = status and nc.update(target, mac_address, public_key,\ timestamp, secured, ssao= (signalgs, verifalgs)) elif NDprotector.mixed_mode == True: warn( "NC updating an entry with an unsecured NA message from the address: %s\n" % packet.src) status = nc.update(target, mac_address, None, None,\ secured, ssao= (signalgs, verifalgs)) else: warn( "NC not updating an entry with an unsecured NA message from the address: %s\n" % packet.src) status = False elif packet.haslayer(ICMPv6ND_RA): if NDprotector.is_router == True: # a router should not receive RA and process them anyway status = False else: status = False # check RSA signature, extract PK certcache = CertCache() # extract the prefix(es) from the RA message prefixes = [] try: next_prefix = packet.getlayer(ICMPv6NDOptPrefixInfo) while next_prefix: if next_prefix.validlifetime != 0 and next_prefix.prefixlen == 64: prefixes.append(next_prefix.prefix) next_prefix = next_prefix.payload.getlayer( ICMPv6NDOptPrefixInfo) except AttributeError: pass warn("RA contains following prefix(es): %s\n" % ` prefixes `) if packet.haslayer(ICMPv6NDOptUSSig): secured = False list_of_pubkeys = CGAPKExtListtoPubKeyList( packet[CGAParams].ext) list_of_pubkeys.insert(0, packet[CGAParams].pubkey) pubkey = list_of_pubkeys[packet[ICMPv6NDOptUSSig].pos] pkhash = get_public_key_hash( pubkey, packet[ICMPv6NDOptUSSig].sigtypeID) if (isinstance(pubkey,PubKey) and \ (NDprotector.min_RSA_key_size <= pubkey.modulusLen and pubkey.modulusLen <= NDprotector.max_RSA_key_size))\ or\ (NDprotector.ECCsupport and isinstance(pubkey, ECCkey)): if CGAverify(packet[IPv6].src,packet.getlayer(CGAParams)) and \ packet[ICMPv6NDOptUSSig].keyh == pkhash and \ packet[ICMPv6NDOptUSSig].verify_sig(pubkey): secured = True else: warn( "an ingoing RA has failed CGA verification or Universal Signature test\n" ) else: warn("A message we received contains" " a Public Key whose size is not allowed\n") # extract the Link-Layer Address from the RA message if any mac_address = None try: mac_address = packet[ICMPv6NDOptSrcLLAddr].lladdr except AttributeError: pass if secured: keyh = packet[ICMPv6NDOptUSSig].keyh if certcache.trustable_hash(keyh, prefixes,\ packet[ICMPv6NDOptUSSig].sigtypeID): warn( "Public Key associated to the signature corresponds to a certificate\n" ) else: # we need to send some CPS in order to recover the certpath secured = False import random warn("Sending a CPS message\n") req_id = random.randrange(1, 0xFFFF) certcache.storeid(req_id) p = Ether(src=get_if_hwaddr(interface),dst=mac_address) / \ IPv6(src="::", dst=packet[IPv6].src)/ \ ICMPv6SEND_CPS(id=req_id,comp=0xffff) sendp(p, iface=interface, verbose=NDprotector.verbose) # we wait for the answers time.sleep(0.4) # we check if the certificate path is now valid secured = certcache.trustable_hash(keyh, prefixes,\ packet[ICMPv6NDOptUSSig].sigtypeID) # check the nonce if the destination address is not ff02::1 if secured and not packet[ IPv6].dst == "ff02::1" and packet.haslayer( ICMPv6NDOptNonce): nonce = packet.getlayer(ICMPv6NDOptNonce).nonce status = nc.check_nonce_in(packet[IPv6].src, packet[IPv6].dst, nonce) elif secured and packet[IPv6].dst == "ff02::1": status = True public_key = str(packet.getlayer(CGAParams).pubkey) timestamp = packet.getlayer(ICMPv6NDOptTimestamp).timestamp if status: warn( "NC updating an entry with an secured RA message from the address: %s\n" % packet.src) status = nc.update(packet[IPv6].src, mac_address, public_key,\ timestamp,secured, ssao= (signalgs, verifalgs)) if status: configured_addresses = nc.dump_addresses() # Address Autoconfiguration part if NDprotector.assign_addresses: # extract the prefix(es) from the RA message that contains the Autonomous flag prefixes_aut = [] try: next_prefix = packet.getlayer( ICMPv6NDOptPrefixInfo) while next_prefix: if next_prefix.prefixlen == 64 \ and next_prefix.A == 1 \ and next_prefix.validlifetime >= next_prefix.preferredlifetime: # we update the prefix cache # (note that the prefix cache will take care # of prefix whose valid lifetime is now 0) nc.update_prefix( next_prefix.prefix, next_prefix.preferredlifetime, next_prefix.validlifetime) # we will do autoconfiguration on these addresses if next_prefix.validlifetime != 0: prefixes_aut.append( (next_prefix.prefix, next_prefix.validlifetime, next_prefix.preferredlifetime)) # set the Autonomous flag to 0 so linux kernel won't # set a new address too next_prefix.A = 0 next_prefix = next_prefix.payload.getlayer( ICMPv6NDOptPrefixInfo) except AttributeError: pass for (prefix, valid, preferred) in prefixes_aut: for address in configured_addresses: # extract the prefix from the address if address.get_prefix() == prefix: # we have a correspondance to this address # refresh the valid and preferred lifetime address.modify(valid, preferred) # let the RA message go break # if no address exists for this prefix else: # create an address for each prefixes new_address = Address( prefix=prefix, key=NDprotector.default_publickey, sec=1, interface=interface, autoconf=True, valid=valid, preferred=preferred) warn( "created a new address (%s) for prefix %s\n" % (str(new_address), prefix)) nc.store_address(new_address) # force checksum calculation del (packet[IPv6].payload.cksum) packet = IPv6(str(packet)) # force checksum calculation # TC 02/19/10: for the record, # that was the previous ugly version # dirty hack to copy the message, except the PIO # let the RA message go , but only after the prefix(es) has been removed # (avoid that kernel autoconfiguration process get involved) # eth = Ether(data) # payld = packet # while payld and not isinstance(payld,NoPayload): # current_layer = payld # try: # payld = payld.payload # except AttributeError: # payld = None # try: # if not isinstance(current_layer,ICMPv6NDOptPrefixInfo): # current_layer.remove_payload() # new_eth /= current_layer # except AttributeError: # payld = None # TC 03/15/10: previous version included a Ethernet header that does not seem appropriate # new_eth = Ether(src=eth.src, dst=eth.dst) / packet # payload.set_verdict_modified(nfqueue.NF_ACCEPT,str(new_eth),len(str(new_eth))) payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(packet), len(str(packet))) return 0 elif NDprotector.mixed_mode: status = True else: status = False elif packet.haslayer(ICMPv6ND_RS): if NDprotector.is_router == True: # if the message does not come from the unspecified address, # perform normal checks if packet[IPv6].src == "::": # nothing to do on a message with the unspecified address as # source address status = True elif packet.haslayer(ICMPv6NDOptUSSig): if not (CGAverify(packet[IPv6].src,packet.getlayer(CGAParams)) and \ packet[ICMPv6NDOptUSSig].keyh == \ get_public_key_hash(packet[CGAParams].pubkey,packet[ICMPv6NDOptUSSig].sigtypeID) and \ packet[ICMPv6NDOptUSSig].verify_sig(packet[CGAParams].pubkey)): warn( "an ingoing packet has failed CGA verification or USO signature test\n" ) status = False else: # record the nonce if any if packet.haslayer(ICMPv6NDOptNonce): nc.record_nonce_in(packet[IPv6].src, packet[IPv6].dst, packet[ICMPv6NDOptNonce].nonce) src_address = packet[IPv6].src mac_address = None try: mac_address = packet.getlayer( ICMPv6NDOptSrcLLAddr).lladdr except AttributeError: pass public_key = str(packet.getlayer(CGAParams).pubkey) timestamp = packet.getlayer(ICMPv6NDOptTimestamp).timestamp warn( "NC updating an entry with an secured RS message from the address: %s\n" % packet.src) status = nc.update(src_address, mac_address, public_key, timestamp, secured=True, ssao=(signalgs, verifalgs)) elif NDprotector.mixed_mode: warn("letting in an unsecured RS message\n") status = True else: warn("dropping an unsecured RS message\n") status = False else: # Host do not process RS messages status = False else: warn("letting in a non NDP message\n") status = True if status: payload.set_verdict(nfqueue.NF_ACCEPT) else: payload.set_verdict(nfqueue.NF_DROP) return 0
def checkcertpath(self, id): """check if a complete cert path is valid, if it is, the last cert is moved to the ApprovedCert list if it isn't, it is discarded""" warn("Verifying certification path for #%d\n" % id) with self.TBApprLock: try: valid_path = False certs, _ = self.TBApprovedCert[id] # removes everything if the last certificate in the chain # is already trusted already_trusted = False with self.ApprLock: for accepted_cert in [ c.output("DER") for c in self.ApprovedCert ]: if accepted_cert == certs[-1].output("DER"): warn( "The Certificate Path we received is already trusted\n" ) already_trusted = True if not already_trusted: # we concat all the cert we got in a new file cert_desc, certfilename = tempfile.mkstemp() valid_IPExt = True if NDprotector.x509_ipextension: # we check that each certificate includes the previous one # each certificate are expected to carry an IP extension # address with only 1s (prev_addr, prev_preflen) = certs[0].IPAddrExt prev_addr = addr_to_int(prev_addr) try: for cert in certs: (addr, preflen) = cert.IPAddrExt addr = addr_to_int(addr) if (addr & prefix_mask(prev_preflen)) == \ (prev_addr & prefix_mask(prev_preflen)) and \ prev_preflen <= preflen : prev_addr = addr prev_preflen = preflen # this prefix is not contained inside its parent's certificate else: warn("Certificate's IP extension does not" " match its parent's Certificate\n") valid_IPExt = False break # if we get in there, it probably means that one certificate is lacking # of IP address extension except TypeError: warn( "At least one certificate in the chain seems " "to lack of the X.509 Extensions for IP Address\n" ) valid_IPExt = False if valid_IPExt: for cert in certs: os.write(cert_desc, cert.output(fmt="PEM")) os.close(cert_desc) for ta in NDprotector.trustanchors: tacert = Cert(ta) # we copy the TA in a temporary file ca_desc, cafilename = tempfile.mkstemp() os.write(ca_desc, tacert.output(fmt="PEM")) os.close(ca_desc) # XXX double check this command # we ask openssl to check the certificate for us cmd = "openssl verify -CAfile %s %s" % ( cafilename, certfilename) res = Popen(cmd, stdout=PIPE, shell=True) output = res.stdout.read() # we clean all the temporary files os.unlink(cafilename) if "OK" in output: valid_path = True break os.unlink(certfilename) if valid_path: warn( "We received a complete and valid Certification Path\n" ) with self.ApprLock: # only the last certificate from the chain is valuable self.ApprovedCert.append(certs[-1]) # either way, we remove the cert path that has been processed del self.TBApprovedCert[id] except KeyError: pass with self.IdLock: try: del self.Id[id] except KeyError: pass
def storeid(self,id): """store the Identifier when sending a CPS""" warn("storing ID %d for a new CPS\n" % id) with self.IdLock: self.Id[id] = TIMEOUT
def callback(i,payload): """a callback function called on each outgoing packets""" data = payload.get_data() packet = IPv6(data) nc = NeighCache() # fetching the latest configured addresses on the interfaces configured_addresses = nc.dump_addresses() if packet.haslayer(ICMPv6ND_NS) \ or packet.haslayer(ICMPv6ND_NA): for addr in configured_addresses: print "Out.py | addr in config %s" % str(addr) # jochoi: debug print "Out.py | source address %s" % packet[IPv6].src # jochoi: debug if str(addr) == packet[IPv6].src: print "Out.py | addr in config matches packet source address" # jochoi: debug if packet.haslayer(ICMPv6ND_NS): nonce = "".join([ chr(random.randrange(255)) for i in range(6)]) nc.record_nonce_out(packet[IPv6].src,packet[IPv6].dst,nonce) else: nonce = nc.pop_nonce_out(packet[IPv6].src,packet[IPv6].dst) data = addr.sign(data,nonce=nonce) # jochoi: split warning for NS and NA messages # warn("signing a NS or NA message\n") if packet.haslayer(ICMPv6ND_NS): warn("signing a NS message\n") else: warn("signing a NA message\n") payload.set_verdict_modified(nfqueue.NF_ACCEPT,str(data),len(str(data))) return 0 else: if NDprotector.mixed_mode: warn("letting go one outgoing unsecured packet\n") payload.set_verdict(nfqueue.NF_ACCEPT) return 0 # added return 0 else: warn("dropping one unsecured packet\n") payload.set_verdict(nfqueue.NF_DROP) return 0 elif packet.haslayer(ICMPv6ND_RS): if NDprotector.is_router == False: if packet[IPv6].src == "::" : payload.set_verdict(nfqueue.NF_ACCEPT) return 0 else: # we need to sign the message for addr in configured_addresses: if str(addr) == packet[IPv6].src: # we generate a nonce for this request nonce = "".join([ chr(random.randrange(255)) for i in range(6)]) nc.record_nonce_out(packet[IPv6].src,packet[IPv6].dst, nonce) warn("signing an outgoing RS message\n") data = addr.sign(data,nonce=nonce) payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(data), len(str(data))) return 0 else: if NDprotector.mixed_mode: warn("letting go one outgoing unsecure RS packet\n") payload.set_verdict(nfqueue.NF_ACCEPT) return 0 else: warn("dropping one unsecure RS packet\n") payload.set_verdict(nfqueue.NF_DROP) return 0 else: # a router does not send this kind of messages payload.set_verdict(nfqueue.NF_DROP) elif packet.haslayer(ICMPv6ND_RA): if NDprotector.is_router: # we need to sign the message for addr in configured_addresses: if str(addr) == packet[IPv6].src: nonce = nc.pop_nonce_out(packet[IPv6].src,packet[IPv6].dst) if nonce != None: nonce = nonce.data warn("signing an outgoing RA message\n") # note that if nonce is None, no nonce value will join this message data = addr.sign(data,nonce=nonce) payload.set_verdict_modified(nfqueue.NF_ACCEPT,str(data),len(str(data))) return 0 else: if NDprotector.mixed_mode: warn("letting go one outgoing unsecure RA packet\n") payload.set_verdict(nfqueue.NF_ACCEPT) return 0 else: warn("dropping one unsecure RA packet\n") payload.set_verdict(nfqueue.NF_DROP) return 0 else: # a host does not send RA messages payload.set_verdict(nfqueue.NF_DROP) return 0 else: warn("letting a non NDP message go out\n") payload.set_verdict(nfqueue.NF_ACCEPT) return 0
def id_match(self,id): """verifies that a Identifier carried in a CPA matches a sent CPS""" warn("checking ID %d against a previously sent CPS\n" % id) with self.IdLock: return id in self.Id
def callback(i, payload): """a callback function called on each ingoing packets""" data = payload.get_data() packet = IPv6(data) # we try to extract the SSAO if available try: (signalgs, verifalgs) = SigAlgList_split(packet[ICMPv6NDOptSSA].sigalgs) except AttributeError: signalgs = [] verifalgs = [] print "In.py | Interface index is : %s" % payload.get_indev() # jochoi: debug print "In.py | Interface name is : %s" % if_indextoname(payload.get_indev()) # jochoi: debug # may be buggy due to the order of get_if_list() # (in that case, we could use the if_nametoindex() ) # interface = get_if_list()[ payload.get_indev() - 1] interface = if_indextoname(payload.get_indev()) # jochoi: edit nc = NeighCache() if packet.haslayer(ICMPv6ND_NS) \ or packet.haslayer(ICMPv6ND_NA): # jochoi: begin debug to match arpsec02 printout if packet.haslayer(ICMPv6ND_NS): print "In.py | Received NS message" else: print "In.py | Received NA message" print "In.py | Source address: %s" % packet[IPv6].src print "In.py | Target address: %s" % packet[IPv6].tgt # jochoi: end debug to match arpsec02 secured = False try: address = packet[IPv6].src # we can receive messages from other node performing a DAD if address == "::": address = packet.tgt print "In.py | About to invoke CGAverify, check 1" # jochoi: debug cga_prop = CGAverify(address,packet.getlayer(CGAParams)) if not cga_prop: warn("an ingoing packet has failed CGA verification test\n") else: # only perform the RSA signature here, # Timestamp and Nonce verification are performed in the NC # FIXME: this is not "good", we are subject to a DoS attack here print "In.py | CGAverify successful" # jochoi: debug list_of_pubkeys = CGAPKExtListtoPubKeyList(packet[CGAParams].ext) list_of_pubkeys.insert(0,packet[CGAParams].pubkey) pubkey = list_of_pubkeys[packet[ICMPv6NDOptUSSig].pos] if isinstance(pubkey, PubKey) and \ not (NDprotector.min_RSA_key_size <= pubkey.modulusLen and pubkey.modulusLen <= NDprotector.max_RSA_key_size): warn("A message we received contains" " a Public Key whose size is not allowed\n") else: # Public Key is of a correct size if packet[ICMPv6NDOptUSSig].keyh !=\ get_public_key_hash(pubkey, packet[ICMPv6NDOptUSSig].sigtypeID) : warn("Public key contained in the CGA PDS does not match " \ "the Public Key hash in the Universal Sig option\n") else: if not packet[ICMPv6NDOptUSSig].verify_sig(pubkey): warn("an ingoing packet has failed Universal Signature verification\n") else: secured = True print "In.py | Set secured to TRUE" # jochoi: debug except AttributeError: warn("An unsecured packet passed\n") # if the status is true, we allow the packet to pass status = False # we can extract the Source Link Layer option from a NS if packet.haslayer(ICMPv6ND_NS): mac_address = None try: mac_address = packet.getlayer(ICMPv6NDOptSrcLLAddr).lladdr except AttributeError: pass # the request does not contain # any information on L2 address if mac_address == None: status = True else: src_address = packet.getlayer(IPv6).src if secured: public_key = str(packet.getlayer(CGAParams).pubkey) timestamp = packet.getlayer(ICMPv6NDOptTimestamp).timestamp warn("NC updating an entry with an secured NS message from the address: %s\n" % packet.src) status = nc.update(src_address, mac_address, public_key, timestamp, secured, ssao= (signalgs, verifalgs)) if not status: warn("NS Message from %s rejected (due to a bad timestamp or nonce)\n" % packet.src) # record the nonce if any if packet.haslayer(ICMPv6NDOptNonce): nc.record_nonce_in(packet[IPv6].src,packet[IPv6].dst, packet[ICMPv6NDOptNonce].nonce) elif NDprotector.mixed_mode == True: warn("NC updating an entry with an unsecured NS message from the address: %s\n" % packet.src) status = nc.update(src_address, mac_address, None, None, secured, ssao= (signalgs, verifalgs)) else: warn("NC not updating an entry with an unsecured NS message from the address: %s\n" % packet.src) status = False # this is a NA, we extract the Target Link Layer option else: target = packet.tgt mac_address = None try: mac_address = packet.getlayer(ICMPv6NDOptDstLLAddr).lladdr except AttributeError: pass if mac_address == None: status = True else: if secured: public_key = str(packet.getlayer(CGAParams).pubkey) timestamp = packet.getlayer(ICMPv6NDOptTimestamp).timestamp try: nonce = packet.getlayer(ICMPv6NDOptNonce).nonce except AttributeError: nonce = None warn("NC updating an entry with an secured NA message from the address: %s\n" % packet.src) # multicasted messages may not contain any nonce as they are sent spontaneously if packet[IPv6].dst != "ff02::1": status = nc.check_nonce_in(packet[IPv6].src,packet[IPv6].dst,nonce) # only update when the nonce is correct status = status and nc.update(target, mac_address, public_key,\ timestamp, secured, ssao= (signalgs, verifalgs)) elif NDprotector.mixed_mode == True: warn("NC updating an entry with an unsecured NA message from the address: %s\n" % packet.src) status = nc.update(target, mac_address, None, None,\ secured, ssao= (signalgs, verifalgs)) else: warn("NC not updating an entry with an unsecured NA message from the address: %s\n" % packet.src) status = False elif packet.haslayer(ICMPv6ND_RA): if NDprotector.is_router == True: # a router should not receive RA and process them anyway status = False else: status = False # check RSA signature, extract PK certcache = CertCache() # extract the prefix(es) from the RA message prefixes = [] try: next_prefix = packet.getlayer(ICMPv6NDOptPrefixInfo) while next_prefix: if next_prefix.validlifetime != 0 and next_prefix.prefixlen == 64: prefixes.append(next_prefix.prefix) next_prefix = next_prefix.payload.getlayer(ICMPv6NDOptPrefixInfo) except AttributeError: pass warn("RA contains following prefix(es): %s\n" % `prefixes`) if packet.haslayer(ICMPv6NDOptUSSig): secured = False list_of_pubkeys = CGAPKExtListtoPubKeyList(packet[CGAParams].ext) list_of_pubkeys.insert(0,packet[CGAParams].pubkey) pubkey = list_of_pubkeys[packet[ICMPv6NDOptUSSig].pos] pkhash = get_public_key_hash(pubkey, packet[ICMPv6NDOptUSSig].sigtypeID) if (isinstance(pubkey,PubKey) and \ (NDprotector.min_RSA_key_size <= pubkey.modulusLen and pubkey.modulusLen <= NDprotector.max_RSA_key_size))\ or\ (NDprotector.ECCsupport and isinstance(pubkey, ECCkey)): print "About to invoke CGAverify in In.py, check 2" # jochoi: debug if CGAverify(packet[IPv6].src,packet.getlayer(CGAParams)) and \ packet[ICMPv6NDOptUSSig].keyh == pkhash and \ packet[ICMPv6NDOptUSSig].verify_sig(pubkey): secured = True else: warn("an ingoing RA has failed CGA verification or Universal Signature test\n") else: warn("A message we received contains" " a Public Key whose size is not allowed\n") # extract the Link-Layer Address from the RA message if any mac_address = None try: mac_address = packet[ICMPv6NDOptSrcLLAddr].lladdr except AttributeError: pass if secured : keyh = packet[ICMPv6NDOptUSSig].keyh if certcache.trustable_hash(keyh, prefixes,\ packet[ICMPv6NDOptUSSig].sigtypeID): warn("Public Key associated to the signature corresponds to a certificate\n") else: # we need to send some CPS in order to recover the certpath secured = False import random warn("Sending a CPS message\n") req_id = random.randrange(1,0xFFFF) certcache.storeid(req_id) p = Ether(src=get_if_hwaddr(interface),dst=mac_address) / \ IPv6(src="::", dst=packet[IPv6].src)/ \ ICMPv6SEND_CPS(id=req_id,comp=0xffff) sendp(p,iface=interface,verbose=NDprotector.verbose) # we wait for the answers time.sleep(0.4) # we check if the certificate path is now valid secured = certcache.trustable_hash(keyh, prefixes,\ packet[ICMPv6NDOptUSSig].sigtypeID) # check the nonce if the destination address is not ff02::1 if secured and not packet[IPv6].dst == "ff02::1" and packet.haslayer(ICMPv6NDOptNonce): nonce = packet.getlayer(ICMPv6NDOptNonce).nonce status = nc.check_nonce_in(packet[IPv6].src,packet[IPv6].dst,nonce) elif secured and packet[IPv6].dst == "ff02::1": status = True public_key = str(packet.getlayer(CGAParams).pubkey) timestamp = packet.getlayer(ICMPv6NDOptTimestamp).timestamp if status: warn("NC updating an entry with an secured RA message from the address: %s\n" % packet.src) status = nc.update(packet[IPv6].src, mac_address, public_key,\ timestamp,secured, ssao= (signalgs, verifalgs)) if status: configured_addresses = nc.dump_addresses() # Address Autoconfiguration part if NDprotector.assign_addresses: # extract the prefix(es) from the RA message that contains the Autonomous flag prefixes_aut = [] try: next_prefix = packet.getlayer(ICMPv6NDOptPrefixInfo) while next_prefix: if next_prefix.prefixlen == 64 \ and next_prefix.A == 1 \ and next_prefix.validlifetime >= next_prefix.preferredlifetime: # we update the prefix cache # (note that the prefix cache will take care # of prefix whose valid lifetime is now 0) nc.update_prefix(next_prefix.prefix, next_prefix.preferredlifetime, next_prefix.validlifetime) # we will do autoconfiguration on these addresses if next_prefix.validlifetime !=0: prefixes_aut.append((next_prefix.prefix, next_prefix.validlifetime, next_prefix.preferredlifetime) ) # set the Autonomous flag to 0 so linux kernel won't # set a new address too next_prefix.A = 0 next_prefix = next_prefix.payload.getlayer(ICMPv6NDOptPrefixInfo) except AttributeError: pass for (prefix, valid, preferred) in prefixes_aut: for address in configured_addresses: # extract the prefix from the address if address.get_prefix() == prefix: # we have a correspondance to this address # refresh the valid and preferred lifetime address.modify(valid,preferred) # let the RA message go break # if no address exists for this prefix else: # create an address for each prefixes new_address = Address(prefix = prefix, key = NDprotector.default_publickey, sec = 1, interface = interface, autoconf = True, valid = valid, preferred = preferred) warn("created a new address (%s) for prefix %s\n" % (str(new_address), prefix)) nc.store_address(new_address) # force checksum calculation del (packet[IPv6].payload.cksum) packet = IPv6(str(packet)) # force checksum calculation # TC 02/19/10: for the record, # that was the previous ugly version # dirty hack to copy the message, except the PIO # let the RA message go , but only after the prefix(es) has been removed # (avoid that kernel autoconfiguration process get involved) # eth = Ether(data) # payld = packet # while payld and not isinstance(payld,NoPayload): # current_layer = payld # try: # payld = payld.payload # except AttributeError: # payld = None # try: # if not isinstance(current_layer,ICMPv6NDOptPrefixInfo): # current_layer.remove_payload() # new_eth /= current_layer # except AttributeError: # payld = None # TC 03/15/10: previous version included a Ethernet header that does not seem appropriate # new_eth = Ether(src=eth.src, dst=eth.dst) / packet # payload.set_verdict_modified(nfqueue.NF_ACCEPT,str(new_eth),len(str(new_eth))) payload.set_verdict_modified(nfqueue.NF_ACCEPT,str(packet),len(str(packet))) return 0 elif NDprotector.mixed_mode: status = True else: status = False elif packet.haslayer(ICMPv6ND_RS): if NDprotector.is_router == True: # if the message does not come from the unspecified address, # perform normal checks if packet[IPv6].src == "::": # nothing to do on a message with the unspecified address as # source address status = True elif packet.haslayer(ICMPv6NDOptUSSig): if not (CGAverify(packet[IPv6].src,packet.getlayer(CGAParams)) and \ packet[ICMPv6NDOptUSSig].keyh == \ get_public_key_hash(packet[CGAParams].pubkey,packet[ICMPv6NDOptUSSig].sigtypeID) and \ packet[ICMPv6NDOptUSSig].verify_sig(packet[CGAParams].pubkey)): warn("an ingoing packet has failed CGA verification or USO signature test\n") status = False else: # record the nonce if any if packet.haslayer(ICMPv6NDOptNonce): nc.record_nonce_in(packet[IPv6].src,packet[IPv6].dst, packet[ICMPv6NDOptNonce].nonce) src_address = packet[IPv6].src mac_address = None try: mac_address = packet.getlayer(ICMPv6NDOptSrcLLAddr).lladdr except AttributeError: pass public_key = str(packet.getlayer(CGAParams).pubkey) timestamp = packet.getlayer(ICMPv6NDOptTimestamp).timestamp warn("NC updating an entry with an secured RS message from the address: %s\n" % packet.src) status = nc.update(src_address, mac_address, public_key, timestamp, secured = True, ssao= (signalgs, verifalgs)) elif NDprotector.mixed_mode: warn("letting in an unsecured RS message\n") status = True else: warn("dropping an unsecured RS message\n") status = False else: # Host do not process RS messages status = False else: warn("letting in a non NDP message\n") status = True if status: payload.set_verdict(nfqueue.NF_ACCEPT) else: payload.set_verdict(nfqueue.NF_DROP) return 0
def checkcertpath(self,id): """check if a complete cert path is valid, if it is, the last cert is moved to the ApprovedCert list if it isn't, it is discarded""" warn("Verifying certification path for #%d\n" % id) with self.TBApprLock: try: valid_path = False certs, _ = self.TBApprovedCert[id] # removes everything if the last certificate in the chain # is already trusted already_trusted = False with self.ApprLock: for accepted_cert in [ c.output("DER") for c in self.ApprovedCert ] : if accepted_cert == certs[-1].output("DER"): warn("The Certificate Path we received is already trusted\n") already_trusted = True if not already_trusted: # we concat all the cert we got in a new file cert_desc, certfilename = tempfile.mkstemp() valid_IPExt = True if NDprotector.x509_ipextension: # we check that each certificate includes the previous one # each certificate are expected to carry an IP extension # address with only 1s (prev_addr,prev_preflen) = certs[0].IPAddrExt prev_addr = addr_to_int(prev_addr) try: for cert in certs: (addr,preflen) = cert.IPAddrExt addr = addr_to_int(addr) if (addr & prefix_mask(prev_preflen)) == \ (prev_addr & prefix_mask(prev_preflen)) and \ prev_preflen <= preflen : prev_addr = addr prev_preflen = preflen # this prefix is not contained inside its parent's certificate else: warn("Certificate's IP extension does not" " match its parent's Certificate\n") valid_IPExt = False break # if we get in there, it probably means that one certificate is lacking # of IP address extension except TypeError: warn("At least one certificate in the chain seems " "to lack of the X.509 Extensions for IP Address\n") valid_IPExt = False if valid_IPExt: for cert in certs: os.write(cert_desc,cert.output(fmt="PEM")) os.close(cert_desc) for ta in NDprotector.trustanchors: tacert = Cert(ta) # we copy the TA in a temporary file ca_desc, cafilename = tempfile.mkstemp() os.write(ca_desc, tacert.output(fmt="PEM")) os.close(ca_desc) # XXX double check this command # we ask openssl to check the certificate for us cmd = "openssl verify -CAfile %s %s" % (cafilename,certfilename) res = Popen(cmd, stdout=PIPE, shell=True) output = res.stdout.read() # we clean all the temporary files os.unlink(cafilename) if "OK" in output: valid_path = True break os.unlink(certfilename) if valid_path: warn("We received a complete and valid Certification Path\n") with self.ApprLock: # only the last certificate from the chain is valuable self.ApprovedCert.append(certs[-1]) # either way, we remove the cert path that has been processed del self.TBApprovedCert[id] except KeyError: pass with self.IdLock: try: del self.Id[id] except KeyError: pass
def id_match(self, id): """verifies that a Identifier carried in a CPA matches a sent CPS""" warn("checking ID %d against a previously sent CPS\n" % id) with self.IdLock: return id in self.Id
def callback(i, payload): """a callback function called on each ingoing packets""" data = payload.get_data() packet = IPv6(data) # if something goes wrong and makes this callback crash, # the packet is dropped payload.set_verdict(nfqueue.NF_DROP) # receiving interface interface = get_if_list()[payload.get_indev() - 1] # extract all the TA option from the CPS/CPA list_of_node_trustanchor = [] ta = packet[ICMPv6NDOptTrustAnchor] while ta: if ta.nametype == 1: # we get the name field of the TA option list_of_node_trustanchor.append(ta.name_field) ta = ta.payload[ICMPv6NDOptTrustAnchor] if NDprotector.is_router: # we have a CPS # (filtering rules were set accordingly) # CPS's message ID req_id = packet[ICMPv6SEND_CPS].id dest_node = packet[IPv6].src if dest_node == "::": # if the origin is the unspecified address # the answer is on the All-Node multicast address dest_node = "ff02::1" src_addr = "::" if packet[IPv6].dst != "ff02::1": src_addr = packet[IPv6].dst else: # lookup for an adress on this interface: nc = NeighCache() configured_addresses = nc.dump_addresses() for address in configured_addresses: if address.get_interface() == interface: src_addr = str(address) break # send multiple as many Certification Path # as there is Trust Anchor options for path in deepcopy(NDprotector.certification_path): trust_anchor = path[0] skip_ta = True if list_of_node_trustanchor == []: skip_ta = False else: # check if this trust anchor is trusted by the node # if it isn't, we check for the next cert in the path while path and skip_ta: trust_anchor = path[0] for ta in list_of_node_trustanchor: # we found the correct trust anchor if ta in str(Cert(trust_anchor)): skip_ta = False break else: try: del path[0] except IndexError: warn( "CertPath.py - callback - this is likely to be a bug\n" ) if skip_ta: # we do not have a Certification Path # down to this Trust Anchor continue # number of certificates to send # (we does not count the TA as we do not send it num_components = len(path) - 2 # send as many CPA as there is certificates in the Certification Path for cert in path[1:]: c = Cert(cert) warn("sending a CPA message\n") p = Ether(src=get_if_hwaddr(interface)) / \ IPv6(src=src_addr,dst=dest_node)/ \ ICMPv6SEND_CPA(id=req_id,comp=num_components,allcomp=len(path) -1)/ \ ICMPv6NDOptCertificate(cert=str(c)) sendp(p, iface=interface, verbose=NDprotector.verbose) num_components -= 1 else: # we have a CPA # connect to the Certificate Cache for future decisions certcache = CertCache() warn("Receiving a CPA message\n") req_id = packet[ICMPv6SEND_CPA].id # we only accept CPA if they are destined to all the nodes or # if they are destined to our node if (packet[IPv6].dst == "ff02::1" and req_id == 0) or certcache.id_match(req_id): lastCPA = (packet[ICMPv6SEND_CPA].comp == 0) # extract all the certificates and feed them to the cache certopt = packet[ICMPv6NDOptCertificate] while certopt: cert = certopt.cert cert = cert.output("PEM") certcache.storecert(req_id, cert) certopt = certopt.payload[ICMPv6NDOptCertificate] # when this is the last CPA message, we ask for the # certificate path validation process if lastCPA: certcache.checkcertpath(req_id)
def callback(i, payload): """a callback function called on each outgoing packets""" data = payload.get_data() packet = IPv6(data) nc = NeighCache() # fetching the latest configured addresses on the interfaces configured_addresses = nc.dump_addresses() if packet.haslayer(ICMPv6ND_NS) \ or packet.haslayer(ICMPv6ND_NA): for addr in configured_addresses: if str(addr) == packet[IPv6].src: if packet.haslayer(ICMPv6ND_NS): nonce = "".join( [chr(random.randrange(255)) for i in range(6)]) nc.record_nonce_out(packet[IPv6].src, packet[IPv6].dst, nonce) else: nonce = nc.pop_nonce_out(packet[IPv6].src, packet[IPv6].dst) data = addr.sign(data, nonce=nonce) warn("signing a NS or NA message\n") payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(data), len(str(data))) return 0 else: if NDprotector.mixed_mode: warn("letting go one outgoing unsecured packet\n") payload.set_verdict(nfqueue.NF_ACCEPT) return else: warn("dropping one unsecured packet\n") payload.set_verdict(nfqueue.NF_DROP) return 0 elif packet.haslayer(ICMPv6ND_RS): if NDprotector.is_router == False: if packet[IPv6].src == "::": payload.set_verdict(nfqueue.NF_ACCEPT) return 0 else: # we need to sign the message for addr in configured_addresses: if str(addr) == packet[IPv6].src: # we generate a nonce for this request nonce = "".join( [chr(random.randrange(255)) for i in range(6)]) nc.record_nonce_out(packet[IPv6].src, packet[IPv6].dst, nonce) warn("signing an outgoing RS message\n") data = addr.sign(data, nonce=nonce) payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(data), len(str(data))) return 0 else: if NDprotector.mixed_mode: warn("letting go one outgoing unsecure RS packet\n") payload.set_verdict(nfqueue.NF_ACCEPT) return 0 else: warn("dropping one unsecure RS packet\n") payload.set_verdict(nfqueue.NF_DROP) return 0 else: # a router does not send this kind of messages payload.set_verdict(nfqueue.NF_DROP) elif packet.haslayer(ICMPv6ND_RA): if NDprotector.is_router: # we need to sign the message for addr in configured_addresses: if str(addr) == packet[IPv6].src: nonce = nc.pop_nonce_out(packet[IPv6].src, packet[IPv6].dst) if nonce != None: nonce = nonce.data warn("signing an outgoing RA message\n") # note that if nonce is None, no nonce value will join this message data = addr.sign(data, nonce=nonce) payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(data), len(str(data))) return 0 else: if NDprotector.mixed_mode: warn("letting go one outgoing unsecure RA packet\n") payload.set_verdict(nfqueue.NF_ACCEPT) return 0 else: warn("dropping one unsecure RA packet\n") payload.set_verdict(nfqueue.NF_DROP) return 0 else: # a host does not send RA messages payload.set_verdict(nfqueue.NF_DROP) return 0 else: warn("letting a non NDP message go out\n") payload.set_verdict(nfqueue.NF_ACCEPT) return 0
def callback(i, payload): """a callback function called on each ingoing packets""" data = payload.get_data() packet = IPv6(data) # if something goes wrong and makes this callback crash, # the packet is dropped payload.set_verdict(nfqueue.NF_DROP) # receiving interface interface = get_if_list()[payload.get_indev() - 1] # extract all the TA option from the CPS/CPA list_of_node_trustanchor = [] ta = packet[ICMPv6NDOptTrustAnchor] while ta: if ta.nametype == 1: # we get the name field of the TA option list_of_node_trustanchor.append(ta.name_field) ta = ta.payload[ICMPv6NDOptTrustAnchor] if NDprotector.is_router: # we have a CPS # (filtering rules were set accordingly) # CPS's message ID req_id = packet[ICMPv6SEND_CPS].id dest_node = packet[IPv6].src if dest_node == "::": # if the origin is the unspecified address # the answer is on the All-Node multicast address dest_node = "ff02::1" src_addr = "::" if packet[IPv6].dst != "ff02::1": src_addr = packet[IPv6].dst else: # lookup for an adress on this interface: nc = NeighCache() configured_addresses = nc.dump_addresses() for address in configured_addresses: if address.get_interface() == interface: src_addr = str(address) break # send multiple as many Certification Path # as there is Trust Anchor options for path in deepcopy(NDprotector.certification_path): trust_anchor = path[0] skip_ta = True if list_of_node_trustanchor == []: skip_ta = False else: # check if this trust anchor is trusted by the node # if it isn't, we check for the next cert in the path while path and skip_ta: trust_anchor = path[0] for ta in list_of_node_trustanchor: # we found the correct trust anchor if ta in str(Cert(trust_anchor)): skip_ta = False break else: try: del path[0] except IndexError: warn("CertPath.py - callback - this is likely to be a bug\n") if skip_ta: # we do not have a Certification Path # down to this Trust Anchor continue # number of certificates to send # (we does not count the TA as we do not send it num_components = len(path) - 2 # send as many CPA as there is certificates in the Certification Path for cert in path[1:]: c = Cert(cert) warn("sending a CPA message\n") p = ( Ether(src=get_if_hwaddr(interface)) / IPv6(src=src_addr, dst=dest_node) / ICMPv6SEND_CPA(id=req_id, comp=num_components, allcomp=len(path) - 1) / ICMPv6NDOptCertificate(cert=str(c)) ) sendp(p, iface=interface, verbose=NDprotector.verbose) num_components -= 1 else: # we have a CPA # connect to the Certificate Cache for future decisions certcache = CertCache() warn("Receiving a CPA message\n") req_id = packet[ICMPv6SEND_CPA].id # we only accept CPA if they are destined to all the nodes or # if they are destined to our node if (packet[IPv6].dst == "ff02::1" and req_id == 0) or certcache.id_match(req_id): lastCPA = packet[ICMPv6SEND_CPA].comp == 0 # extract all the certificates and feed them to the cache certopt = packet[ICMPv6NDOptCertificate] while certopt: cert = certopt.cert cert = cert.output("PEM") certcache.storecert(req_id, cert) certopt = certopt.payload[ICMPv6NDOptCertificate] # when this is the last CPA message, we ask for the # certificate path validation process if lastCPA: certcache.checkcertpath(req_id)
def storeid(self, id): """store the Identifier when sending a CPS""" warn("storing ID %d for a new CPS\n" % id) with self.IdLock: self.Id[id] = TIMEOUT