Example #1
0
 def __init__(self, cfg):
     sec_level = cfg.collectd_security_level
     if sec_level in ("sign", "SIGN", "Sign", 1):
         self.sec_level = 1
     elif sec_level in ("encrypt", "ENCRYPT", "Encrypt", 2):
         self.sec_level = 2
     else:
         self.sec_level = 0
     self.auth_file = cfg.collectd_auth_file
     self.auth_db = {}
     self.cfg_mon = None
     if self.auth_file:
         self.load_auth_file()
         self.cfg_mon = FileMonitor(self.auth_file)
     if self.sec_level:
         if not self.auth_file:
             raise ConfigError("Collectd security level configured but no "
                               "auth file specified in configuration")
         if not self.auth_db:
             log.warning("Collectd security level configured but no "
                         "user/passwd entries loaded from auth file")
Example #2
0
 def __init__(self, cfg):
     sec_level = cfg.collectd_security_level
     if sec_level in ("sign", "SIGN", "Sign", 1):
         self.sec_level = 1
     elif sec_level in ("encrypt", "ENCRYPT", "Encrypt", 2):
         self.sec_level = 2
     else:
         self.sec_level = 0
     self.auth_file = cfg.collectd_auth_file
     self.auth_db = {}
     self.cfg_mon = None
     if self.auth_file:
         self.load_auth_file()
         self.cfg_mon = FileMonitor(self.auth_file)
     if self.sec_level:
         if not self.auth_file:
             raise ConfigError("Collectd security level configured but no " "auth file specified in configuration")
         if not self.auth_db:
             log.warning("Collectd security level configured but no " "user/passwd entries loaded from auth file")
Example #3
0
class CollectDCrypto(object):
    def __init__(self, cfg):
        sec_level = cfg.collectd_security_level
        if sec_level in ("sign", "SIGN", "Sign", 1):
            self.sec_level = 1
        elif sec_level in ("encrypt", "ENCRYPT", "Encrypt", 2):
            self.sec_level = 2
        else:
            self.sec_level = 0
        self.auth_file = cfg.collectd_auth_file
        self.auth_db = {}
        self.cfg_mon = None
        self.crypto_backend = default_backend()
        if self.auth_file:
            self.load_auth_file()
            self.cfg_mon = FileMonitor(self.auth_file)
        if self.sec_level:
            if not self.auth_file:
                raise ConfigError("Collectd security level configured but no "
                                  "auth file specified in configuration")
            if not self.auth_db:
                log.warning("Collectd security level configured but no "
                            "user/passwd entries loaded from auth file")

    def load_auth_file(self):
        try:
            f = open(self.auth_file)
        except IOError as exc:
            raise ConfigError("Unable to load collectd's auth file: %r" % exc)
        self.auth_db.clear()
        for line in f:
            line = line.strip()
            if not line or line[0] == "#":
                continue
            user, passwd = line.split(":", 1)
            user = user.strip()
            passwd = passwd.strip()
            if not user or not passwd:
                log.warning("Found line with missing user or password")
                continue
            if user in self.auth_db:
                log.warning("Found multiple entries for single user")
            self.auth_db[user] = passwd
        f.close()
        log.info("Loaded collectd's auth file from %s", self.auth_file)

    def parse(self, data):
        if len(data) < 4:
            raise ProtocolError("Truncated header.")
        part_type, part_len = struct.unpack("!HH", data[:4])
        sec_level = {0x0200: 1, 0x0210: 2}.get(part_type, 0)
        if sec_level < self.sec_level:
            raise ProtocolError("Packet has lower security level than allowed")
        if not sec_level:
            return data
        if sec_level == 1 and not self.sec_level:
            return data[part_len:]
        data = data[4:]
        part_len -= 4
        if len(data) < part_len:
            raise ProtocolError("Truncated part payload.")
        if self.cfg_mon is not None and self.cfg_mon.modified():
            log.info("Collectd authfile modified, reloading")
            self.load_auth_file()
        if sec_level == 1:
            return self.parse_signed(part_len, data)
        if sec_level == 2:
            return self.parse_encrypted(part_len, data)

    def parse_signed(self, part_len, data):
        if part_len <= 32:
            raise ProtocolError("Truncated signed part.")
        sig, data = data[:32], data[32:]
        uname_len = part_len - 32
        uname = data[:uname_len].decode()
        if uname not in self.auth_db:
            raise ProtocolError("Signed packet, unknown user '%s'" % uname)
        password = self.auth_db[uname].encode()
        sig2 = hmac.new(password, msg=data, digestmod=sha256).digest()
        if not self._hashes_match(sig, sig2):
            raise ProtocolError("Bad signature from user '%s'" % uname)
        data = data[uname_len:]
        return data

    def parse_encrypted(self, part_len, data):
        if part_len != len(data):
            raise ProtocolError("Enc pkt size disaggrees with header.")
        if len(data) <= 38:
            raise ProtocolError("Truncated encrypted part.")
        uname_len, data = struct.unpack("!H", data[:2])[0], data[2:]
        if len(data) <= uname_len + 36:
            raise ProtocolError("Truncated encrypted part.")
        uname, data = data[:uname_len].decode(), data[uname_len:]
        if uname not in self.auth_db:
            raise ProtocolError("Couldn't decrypt, unknown user '%s'" % uname)
        iv, data = data[:16], data[16:]
        password = self.auth_db[uname].encode()
        key = sha256(password).digest()
        pad_bytes = 16 - (len(data) % 16)
        data += b'\0' * pad_bytes
        cipher = Cipher(algorithms.AES(key), modes.OFB(iv), backend=self.crypto_backend)
        decryptor = cipher.decryptor()
        data = decryptor.update(data)
        data = data[:-pad_bytes]
        tag, data = data[:20], data[20:]
        tag2 = sha1(data).digest()
        if not self._hashes_match(tag, tag2):
            raise ProtocolError("Bad checksum on enc pkt for '%s'" % uname)
        return data

    def _hashes_match(self, a, b):
        """Constant time comparison of bytes for py3, strings for py2"""
        if len(a) != len(b):
            return False
        diff = 0
        if six.PY2:
            a = bytearray(a)
            b = bytearray(b)
        for x, y in zip(a, b):
            diff |= x ^ y
        return not diff