Ejemplo n.º 1
0
 def add_denylist(self, deny):
     self.__allowdenylist_lock.acquire()
     try:
         pk, _ = process_chain(deny,
                               None,
                               'key-denylist',
                               allowlist=self.__allowlist,
                               denylist=self.__denylist)
         if (len(pk) >= 4):
             apk = msgpack.unpackb(pk[3], raw=True)
             if pk[2] != apk[2]:
                 self._logger.info(
                     "Mismatch in keys for denylist %s vs %s.", pk[2].hex(),
                     apk[2].hex())
                 raise ValueError("Mismatch in keys for denylist.")
             if key_in_list(pk[3], self.__denylist) == None:
                 self.__denylist.append(pk[3])
             else:
                 self._logger.info("Key %s already in denylist",
                                   pk[2].hex())
                 raise ValueError("Key already in denylist!")
             self._logger.warning("Added key %s to denylist", pk[2].hex())
             return pk[3]
     except Exception as e:
         self._logger.warning("".join(
             traceback.format_exception(etype=type(e),
                                        value=e,
                                        tb=e.__traceback__)))
         return None
     finally:
         self.__allowdenylist_lock.release()
Ejemplo n.º 2
0
 def reencrypt_request(self,
                       topic,
                       cryptoexchange,
                       msgkey=None,
                       msgval=None):
     if (isinstance(topic, (bytes, bytearray))):
         self._logger.debug("topic provided as bytes (should be string)")
         topic = topic.decode('utf-8')
     #
     # msgval should be a msgpacked chain chaining to a provisioner.
     # The last item in the array is the encrypted public key to retransmit.
     #
     try:
         pk = None
         try:
             pk, pkprint = process_chain(msgval,
                                         topic,
                                         'key-encrypt-subscribe',
                                         allowlist=self.__allowlist,
                                         denylist=self.__denylist)
         except ProcessChainError as pce:
             raise pce
         except:
             pass
         if (pk is None):
             if not (pkprint is None):
                 raise ProcessChainError("Request did not validate: ",
                                         pkprint)
             else:
                 raise ValueError("Request did not validate!")
         msg = cryptoexchange.signed_epk(topic, epk=pk[2])
     except Exception as e:
         self._logger.warning("".join(format_exception_shim(e)))
         return (None, None)
     return (msgkey, msg)
Ejemplo n.º 3
0
 def __update_spk_chain(self, newchain):
   #
   # We have a new candidate chain to replace the current one (if any). We
   # first must check it is a valid chain ending in our signing public key.
   #
   if (newchain is None):
     return None
   try:
     with self.__allowdenylist_lock:
       pk,pkprint = process_chain(newchain,None,None,allowlist=self.__allowlist,denylist=self.__denylist)
     if (len(pk) < 3 or pk[2] != self.__cryptokey.get_spk()):
       if not (pkprint is None):
         raise ProcessChainError("New chain does not match current signing publickey:", pkprint)
       else:
         raise ValueError("New chain does not match current signing public key,")
     # If we get here, it is a valid chain. So now we need
     # to see if it is "superior" than our current chain.
     # This means a larger minimum max_age.
     min_max_age = 0
     with self.__spk_chain_lock:
       for cpk in self.__spk_chain:
         if (min_max_age == 0 or cpk[0]<min_max_age):
           min_max_age = cpk[0]
     if (pk[0] < min_max_age):
       raise ProcessChainError("New chain has shorter expiry time than current chain.", pkprint)
     self.__spk_chain = msgpack.unpackb(newchain,raw=True)
     self._logger.warning("Utilizing new chain: %s", str(pkprint))
     # Check if we are capable of direct key requests (default is "no")
     with self.__spk_chain_lock:
       self.__spk_direct_request = False
       self._logger.debug("Defaulting new chain to not handle direct requests.")
     try:
       with self.__allowdenylist_lock:
         pk,_ = process_chain(newchain,None,'key-encrypt-request',allowlist=self.__allowlist,denylist=self.__denylist)
       if len(pk) >= 3:
         with self.__spk_chain_lock:
           self._logger.info("  New chain supports direct key requests. Enabling.")
           self.__spk_direct_request = True
     except Exception as e:
       # exceptions when checking direct mean it is not supported
       with self.__spk_chain_lock:
         self._logger.info("  New chain does not support direct key requests. Disabling.")
         self.__spk_direct_request = False
     return newchain
   except Exception as e:
     self._logger.warning("".join(format_exception_shim(e)))
     return None
Ejemplo n.º 4
0
 def encrypt_keys(self, keyidxs, keys, topic, msgval=None):
   if (isinstance(topic,(bytes,bytearray))):
     self._logger.debug("passed a topic in bytes (should be string)")
     topic = topic.decode('utf-8')
   #
   # msgval should be a msgpacked chain.
   # The public key in the array is the public key to send the key to, using a
   # common DH-derived key between that public key and our private encryption key.
   # Then there is at least one more additional item, random bytes:
   # (3) random bytes
   # Currently items after this are ignored, and reserved for future use.
   #
   try:
     with self.__allowdenylist_lock:
       pk,_ = process_chain(msgval,topic,'key-encrypt-request',allowlist=self.__allowlist,denylist=self.__denylist)
     # Construct shared secret as sha256(topic || random0 || random1 || our_private*their_public)
     epk = self.__cryptokey.get_epk(topic, 'encrypt_keys')
     pks = [pk[2]]
     eks = self.__cryptokey.use_epk(topic, 'encrypt_keys',pks)
     ek = eks[0]
     eks[0] = epk
     random0 = pk[3]
     random1 = pysodium.randombytes(self.__randombytes)
     ss = pysodium.crypto_hash_sha256(topic.encode('utf-8') + random0 + random1 + ek)[0:pysodium.crypto_secretbox_KEYBYTES]
     nonce = pysodium.randombytes(pysodium.crypto_secretbox_NONCEBYTES)
     # encrypt keys and key indexes (MAC appended, nonce prepended)
     msg = []
     for i in range(0,len(keyidxs)):
       msg.append(keyidxs[i])
       msg.append(keys[i])
     msg = msgpack.packb(msg, use_bin_type=True)
     msg = nonce + pysodium.crypto_secretbox(msg,nonce,ss)
     # this is then put in a msgpack array with the appropriate max_age, poison, and public key(s)
     poison = msgpack.packb([['topics',[topic]],['usages',['key-encrypt']]], use_bin_type=True)
     msg = msgpack.packb([time()+self.__maxage,poison,eks,[random0,random1],msg], use_bin_type=True)
     # and signed with our signing key
     msg = self.__cryptokey.sign_spk(msg)
     # and finally put as last member of a msgpacked array chaining to ROT
     with self.__spk_chain_lock:
       tchain = self.__spk_chain.copy()
       if (len(tchain) == 0):
         poison = msgpack.packb([['topics',[topic]],['usages',['key-encrypt']],['pathlen',1]], use_bin_type=True)
         lastcert = msgpack.packb([time()+self.__maxage,poison,self.__cryptokey.get_spk()], use_bin_type=True)
         _,tempsk = pysodium.crypto_sign_seed_keypair(unhexlify(b'4c194f7de97c67626cc43fbdaf93dffbc4735352b37370072697d44254e1bc6c'))
         tchain.append(pysodium.crypto_sign(lastcert,tempsk))
         provision = msgpack.packb([msgpack.packb([0,b'\x90',self.__cryptokey.get_spk()]),self.__cryptokey.sign_spk(lastcert)], use_bin_type=True)
         self._logger.warning("Current signing chain is empty. Use %s to provision access and then remove temporary root of trust from allowedlist.", provision.hex())
     tchain.append(msg)
     msg = msgpack.packb(tchain, use_bin_type=True)
   except Exception as e:
     self._logger.warning("".join(format_exception_shim(e)))
     return None
   return msg
Ejemplo n.º 5
0
 def decrypt_keys(self, topic, msgval=None):
   if (isinstance(topic,(bytes,bytearray))):
     self._logger.debug("passed a topic in bytes (should be string)")
     topic = topic.decode('utf-8')
   #
   # msgval should be a msgpacked chain.
   # The public key in the array is a set of public key(s) to combine with our
   # encryption key to get the secret to decrypt the key. If we do not
   # have an encryption key for the topic, we cannot do it until we have one.
   # The next item is then the pair of random values for generating the shared
   # secret, followed by the actual key message.
   #
   try:
     with self.__allowdenylist_lock:
       pk,pkprint = process_chain(msgval,topic,'key-encrypt',allowlist=self.__allowlist,denylist=self.__denylist)
     if (len(pk) < 5):
       if not (pkprint is None):
         raise ProcessChainError("Unexpected number of chain elements:", pkprint)
       else:
         raise ValueError("Unexpected number of chain elements!")
     random0 = pk[3][0]
     random1 = pk[3][1]
     nonce = pk[4][0:pysodium.crypto_secretbox_NONCEBYTES]
     msg = pk[4][pysodium.crypto_secretbox_NONCEBYTES:]
     eks = self.__cryptokey.use_epk(topic, 'decrypt_keys', pk[2], clear=False)
     for ck in eks:
       # Construct candidate shared secrets as sha256(topic || random0 || random1 || our_private*their_public)
       ss = pysodium.crypto_hash_sha256(topic.encode('utf-8') + random0 + random1 + ck)[0:pysodium.crypto_secretbox_KEYBYTES]
       # decrypt and return key
       try:
         msg = msgpack.unpackb(pysodium.crypto_secretbox_open(msg,nonce,ss),raw=True)
         rvs = {}
         for i in range(0,len(msg),2):
           rvs[msg[i]] = msg[i+1]
         if len(rvs) < 1 or 2*len(rvs) != len(msg):
           raise ValueError
       except:
         pass
       else:
         # clear the esk/epk we just used
         self.__cryptokey.use_epk(topic, 'decrypt_keys', [])
         return rvs
     raise ValueError
   except Exception as e:
     self._logger.warning("".join(format_exception_shim(e)))
     pass
   return None
Ejemplo n.º 6
0
 def __update_spk_chain(self, newchain):
     #
     # We have a new candidate chain to replace the current one (if any). We
     # first must check it is a valid chain ending in our signing public key.
     #
     if (newchain is None):
         return None
     try:
         with self.__allowdenylist_lock:
             pk, pkprint = process_chain(newchain,
                                         None,
                                         None,
                                         allowlist=self.__allowlist,
                                         denylist=self.__denylist)
         if (len(pk) < 3 or pk[2] != self.__cryptokey.get_spk()):
             if not (pkprint is None):
                 raise ProcessChainError(
                     "New chain does not match current signing publickey:",
                     pkprint)
             else:
                 raise ValueError(
                     "New chain does not match current signing public key,")
         # If we get here, it is a valid chain. So now we need
         # to see if it is "superior" than our current chain.
         # This means a larger minimum max_age.
         with self.__spk_chain_lock:
             min_max_age = 0
             for cpk in self.__spk_chain:
                 if (min_max_age == 0 or cpk[0] < min_max_age):
                     min_max_age = cpk[0]
             if (pk[0] < min_max_age):
                 raise ProcessChainError(
                     "New chain has shorter expiry time than current chain.",
                     pkprint)
             self.__spk_chain = msgpack.unpackb(newchain, raw=True)
             self._logger.warning("Utilizing new chain: %s", str(pkprint))
             return newchain
     except Exception as e:
         self._logger.warning("".join(
             traceback.format_exception(etype=type(e),
                                        value=e,
                                        tb=e.__traceback__)))
         return None
Ejemplo n.º 7
0
 def _chain_server(self):
     while True:
         self._logger.info("Checking Key Lifetimes")
         chainkeys = self._cryptostore.load_section('chainkeys',
                                                    defaults=False)
         for ck in chainkeys.keys():
             cv = msgpack.unpackb(chainkeys[ck], raw=True)
             self._logger.info("Checking Key: %s", cv[2])
             if cv[0] < time() + self._lifetime * self._refresh_fraction:
                 try:
                     # Time to renew this key
                     self._logger.warning("Key expires soon, renewing %s",
                                          cv)
                     msg = msgpack.packb(
                         [time() + self._lifetime, cv[1], cv[2]],
                         use_bin_type=True)
                     chain = self._cryptokey.sign_spk(msg)
                     chain = msgpack.packb(
                         msgpack.unpackb(self._our_chain, raw=False) +
                         [chain],
                         use_bin_type=True)
                     # Validate
                     pk, _ = process_chain(chain,
                                           None,
                                           None,
                                           allowlist=self._allowlist)
                     # Broadcast
                     self._producer.send('chains', key=cv[2], value=chain)
                     self._producer.flush(timeout=self._flush_time)
                     # save
                     self._cryptostore.store_value(ck,
                                                   msg,
                                                   section='chainkeys')
                 except Exception as e:
                     self._logger.warning("".join(format_exception_shim(e)))
         self._logger.info("Done Checking.")
         sleep(self._interval_secs)
Ejemplo n.º 8
0
    password = ''
    while len(password) < 12:
        password = getpass('Provisioning Password (12+ chars): ')
    prov = PasswordProvisioner(password, _rot)

    # Check we have appropriate chains
    if (choice == 1):
        # Controllers must be signed by ROT
        _msgchkrot = _msgrot
    else:
        # Everyone else by the Chain ROT (may = ROT)
        _msgchkrot = _msgchainrot
    assert (
        len(_msgchains[key]) > 0
    ), 'A trusted chain for ' + key + ' is missing. Use generate-chains.py (and possibly sign with another key), and add to provision.py.'
    pk = process_chain(_msgchains[key], None, None, allowlist=[_msgchkrot])
    assert (
        len(pk) >= 3 and pk[2] == prov._pk[_keys[key]]
    ), 'Malformed chain for ' + key + '. Did you enter your password correctly and have msgchain rot set appropriately?'

topics = None
while topics is None:
    topic = input('Enter a space separated list of topics this ' + key +
                  ' will use: ')
    topics = list(set(map(lambda i: i.split('.', 1)[0], topic.split())))
    if (len(topics) < 1):
        ans = input('Are you sure you want to allow all topics (y/N)?')
        if (len(ans) == 0 or ans[0].lower() != 'y'):
            topics = None
        else:
            topics = ['^.*$']
Ejemplo n.º 9
0
elif (choice == 4 and not limited):
    key = 'prodcon'
else:
    assert False, 'Invalid combination of choices!'

# Check we have appropriate chains
if (choice == 1):
    # Controllers must be signed by ROT
    _msgchkrot = _msgrot
else:
    # Everyone else by Chain ROT (may = ROT)
    _msgchkrot = _msgchainrot
assert (
    len(_msgchains[key]) > 0
), 'A trusted chain for ' + key + ' is missing. This should not happen with simple-provision, please report as a bug.'
pk = process_chain(_msgchains[key], None, None, allowlist=[_msgchkrot])[0]
assert (
    len(pk) >= 3 and pk[2] == prov._pk[_keys[key]]
), 'Malformed chain for ' + key + '. Did you enter your passwords correctly?'

topics = None
while topics is None:
    topic = input('Enter a space separated list of topics this ' + key +
                  ' will use: ')
    topics = list(set(map(lambda i: i.split('.', 1)[0], topic.split())))
    if (len(topics) < 1):
        ans = input('Are you sure you want to allow all topics (Y/n)?')
        if len(ans) > 0 and (ans[0].lower() == 'n'):
            topics = None
        else:
            topics = ['^.*$']
Ejemplo n.º 10
0
    def __init__(self, nodeID, config=None, cryptokey=None):
        if ((not isinstance(nodeID, (str)) or len(nodeID) < 1)
                and (nodeID != None or config is None)):
            raise KafkaCryptoChainServerError(
                "Node ID " + str(nodeID) + " not a string or not specified!")
        if (config is None):
            config = nodeID + ".config"

        self._logger = logging.getLogger(__name__)

        if (hasattr(config, 'load_section')
                and inspect.isroutine(config.load_section)
                and hasattr(config, 'load_value')
                and inspect.isroutine(config.load_value)
                and hasattr(config, 'store_value')
                and inspect.isroutine(config.store_value)
                and hasattr(config, 'load_opaque_value')
                and inspect.isroutine(config.load_opaque_value)
                and hasattr(config, 'store_opaque_value')
                and inspect.isroutine(config.store_opaque_value)
                and hasattr(config, 'set_cryptokey')
                and inspect.isroutine(config.set_cryptokey)):
            self._cryptostore = config
        else:
            self._cryptostore = CryptoStore(nodeID, config)
        nodeID = self._cryptostore.get_nodeID()
        self._nodeID = nodeID

        if (cryptokey is None):
            cryptokey = self._cryptostore.load_value('cryptokey')
            if cryptokey.startswith('file#'):
                cryptokey = cryptokey[5:]
        if (isinstance(cryptokey, (str))):
            cryptokey = CryptoKey(file=cryptokey)
        if (not hasattr(cryptokey, 'get_spk')
                or not inspect.isroutine(cryptokey.get_spk)
                or not hasattr(cryptokey, 'sign_spk')
                or not inspect.isroutine(cryptokey.sign_spk)
                or not hasattr(cryptokey, 'get_epk')
                or not inspect.isroutine(cryptokey.get_epk)
                or not hasattr(cryptokey, 'use_epk')
                or not inspect.isroutine(cryptokey.use_epk)
                or not hasattr(cryptokey, 'wrap_opaque')
                or not inspect.isroutine(cryptokey.wrap_opaque)
                or not hasattr(cryptokey, 'unwrap_opaque')
                or not inspect.isroutine(cryptokey.unwrap_opaque)):
            raise KafkaCryptoChainServerError(
                "Invalid cryptokey source supplied!")
        self._cryptokey = cryptokey
        self._cryptostore.set_cryptokey(self._cryptokey)

        # Load our custom configuration
        self._interval_secs = self._cryptostore.load_value('interval_secs',
                                                           default=300)
        self._lifetime = self._cryptostore.load_value('lifetime',
                                                      default=604800)
        self._refresh_fraction = self._cryptostore.load_value(
            'refresh_fraction', default=0.143)
        self._flush_time = self._cryptostore.load_value('flush_time',
                                                        default=2.0)

        # Load our signing key and trimmings
        self._our_chain = self._cryptostore.load_value('chain',
                                                       section='crypto')
        try:
            msgpack.unpackb(self._our_chain, raw=False)
        except:
            self._logger.warning(
                "Chain server chain is in legacy format. This should be corrected."
            )
            self._our_chain = msgpack.packb(msgpack.unpackb(self._our_chain,
                                                            raw=True),
                                            use_bin_type=True)
        self._allowlist = self._cryptostore.load_section('allowlist',
                                                         defaults=False)
        if not (self._allowlist is None):
            self._allowlist = self._allowlist.values()

        # Validate
        pk, pkprint = process_chain(self._our_chain,
                                    None,
                                    None,
                                    allowlist=self._allowlist)
        if (pk[2] != self._cryptokey.get_spk()):
            raise KafkaCryptoChainServerError(
                "Chain does not match public key: " + str(pkprint))

        # Connect to Kafka
        self._producer = KafkaProducer(
            **self._cryptostore.get_kafka_config('producer'))

        # Run Thread
        self._mgmt_thread = Thread(target=self._chain_server, daemon=True)
        self._mgmt_thread.start()