"""
Gnome keyring parser.

Sources:
 - Gnome Keyring source code,
   function generate_file() in keyrings/gkr-keyring.c,

Author: Victor Stinner
Creation date: 2008-04-09
"""

from hachoir.core.tools import paddingSize
from hachoir.parser import Parser
from hachoir.field import (FieldSet,
                           Bit, NullBits, NullBytes,
                           UInt8, UInt32, String, RawBytes, Enum,
                           TimestampUnix64, CompressedField,
                           SubFile)
from hachoir.core.endian import BIG_ENDIAN

try:
    import hashlib

    def sha256(data):
        hash = hashlib.new('sha256')
        hash.update(data)
        return hash.digest()
except ImportError:
    def sha256(data):
        raise ImportError("hashlib module is missing")

try:
    from Crypto.Cipher import AES

    class DeflateStream:

        def __init__(self, stream):
            hash_iterations = 1234
            password = "x" * 8
            salt = "\0" * 8
            key, iv = generate_key(password, salt, hash_iterations)
            self.cipher = AES.new(key, AES.MODE_CBC, iv)

        def __call__(self, size, data=None):
            if data is None:
                return ''
            return self.cipher.decrypt(data)

    def Deflate(field):
        CompressedField(field, DeflateStream)
        return field
except ImportError:
    def Deflate(field):
        return field


class KeyringString(FieldSet):

    def createFields(self):
        yield UInt32(self, "length")
        length = self["length"].value
        if length == 0xffffffff:
            return
        yield String(self, "text", length, charset="UTF-8")

    def createValue(self):
        if "text" in self:
            return self["text"].value
        else:
            return ''

    def createDescription(self):
        if "text" in self:
            return self["text"].value
        else:
            return "(empty string)"


class Attribute(FieldSet):

    def createFields(self):
        yield KeyringString(self, "name")
        yield UInt32(self, "type")
        type = self["type"].value
        if type == 0:
            yield KeyringString(self, "value")
        elif type == 1:
            yield UInt32(self, "value")
        else:
            raise TypeError("Unknown attribute type (%s)" % type)

    def createDescription(self):
        return 'Attribute "%s"' % self["name"].value


class ACL(FieldSet):

    def createFields(self):
        yield UInt32(self, "types_allowed")
        yield KeyringString(self, "display_name")
        yield KeyringString(self, "pathname")
        yield KeyringString(self, "reserved[]")
        yield UInt32(self, "reserved[]")


class Item(FieldSet):

    def createFields(self):
        yield UInt32(self, "id")
        yield UInt32(self, "type")
        yield UInt32(self, "attr_count")
        for index in range(self["attr_count"].value):
            yield Attribute(self, "attr[]")

    def createDescription(self):
        return "Item #%s: %s attributes" % (self["id"].value, self["attr_count"].value)


class Items(FieldSet):

    def createFields(self):
        yield UInt32(self, "count")
        for index in range(self["count"].value):
            yield Item(self, "item[]")


class EncryptedItem(FieldSet):

    def createFields(self):
        yield KeyringString(self, "display_name")
        yield KeyringString(self, "secret")
        yield TimestampUnix64(self, "mtime")
        yield TimestampUnix64(self, "ctime")
        yield KeyringString(self, "reserved[]")
        for index in range(4):
            yield UInt32(self, "reserved[]")
        yield UInt32(self, "attr_count")
        for index in range(self["attr_count"].value):
            yield Attribute(self, "attr[]")
        yield UInt32(self, "acl_count")
        for index in range(self["acl_count"].value):
            yield ACL(self, "acl[]")
#        size = 8 # paddingSize((self.stream.size - self.current_size) // 8, 16)
#        if size:
#            yield NullBytes(self, "hash_padding", size, "16 bytes alignment")


class EncryptedData(Parser):
    PARSER_TAGS = {
        "id": "gnomeencryptedkeyring",
        "min_size": 16 * 8,
        "description": "Gnome encrypted keyring",
    }
    endian = BIG_ENDIAN

    def validate(self):
        return True

    def createFields(self):
        yield RawBytes(self, "md5", 16)
        while True:
            size = (self.size - self.current_size) // 8
            if size < 77:
                break
            yield EncryptedItem(self, "item[]")
        size = paddingSize(self.current_size // 8, 16)
        if size:
            yield NullBytes(self, "padding_align", size)


class GnomeKeyring(Parser):
    MAGIC = b"GnomeKeyring\n\r\0\n"
    PARSER_TAGS = {
        "id": "gnomekeyring",
        "category": "misc",
        "magic": ((MAGIC, 0),),
        "min_size": 47 * 8,
        "description": "Gnome keyring",
    }
    CRYPTO_NAMES = {
        0: "AEL",
    }
    HASH_NAMES = {
        0: "MD5",
    }

    endian = BIG_ENDIAN

    def validate(self):
        if self.stream.readBytes(0, len(self.MAGIC)) != self.MAGIC:
            return "Invalid magic string"
        return True

    def createFields(self):
        yield String(self, "magic", len(self.MAGIC), 'Magic string (%r)' % self.MAGIC, charset="ASCII")
        yield UInt8(self, "major_version")
        yield UInt8(self, "minor_version")
        yield Enum(UInt8(self, "crypto"), self.CRYPTO_NAMES)
        yield Enum(UInt8(self, "hash"), self.HASH_NAMES)
        yield KeyringString(self, "keyring_name")
        yield TimestampUnix64(self, "mtime")
        yield TimestampUnix64(self, "ctime")
        yield Bit(self, "lock_on_idle")
        yield NullBits(self, "reserved[]", 31, "Reserved for future flags")
        yield UInt32(self, "lock_timeout")
        yield UInt32(self, "hash_iterations")
        yield RawBytes(self, "salt", 8)
        yield NullBytes(self, "reserved[]", 16)
        yield Items(self, "items")
        yield UInt32(self, "encrypted_size")
        yield Deflate(SubFile(self, "encrypted", self["encrypted_size"].value, "AES128 CBC", parser_class=EncryptedData))


def generate_key(password, salt, hash_iterations):
    sha = sha256(password + salt)
    for index in range(hash_iterations - 1):
        sha = sha256(sha)
    return sha[:16], sha[16:]
