1"""
2Contains functions to decode, encode and generate keys.
3"""
4import enum
5import hashlib
6import hmac
7
8import libnacl.encode
9import libnacl.public
10import libnacl.secret
11
12from .exception import GatewayKeyError
13
14__all__ = (
15    'HMAC',
16    'Key',
17)
18
19
20class HMAC:
21    """
22    A collection of HMAC functions used for the gateway service.
23    """
24    keys = {
25        'email': b'\x30\xa5\x50\x0f\xed\x97\x01\xfa\x6d\xef\xdb\x61\x08\x41\x90\x0f'
26                 b'\xeb\xb8\xe4\x30\x88\x1f\x7a\xd8\x16\x82\x62\x64\xec\x09\xba\xd7',
27        'phone': b'\x85\xad\xf8\x22\x69\x53\xf3\xd9\x6c\xfd\x5d\x09\xbf\x29\x55\x5e'
28                 b'\xb9\x55\xfc\xd8\xaa\x5e\xc4\xf9\xfc\xd8\x69\xe2\x58\x37\x07\x23'
29    }
30
31    @staticmethod
32    def hash(message, hash_type):
33        """
34        Generate the hash for a message type.
35
36        Arguments:
37            - `message`: A message.
38            - `hash_type`: `email` or `phone`.
39
40        Return a :class:`hmac.HMAC` instance.
41        """
42        return hmac.new(HMAC.keys[hash_type], message.encode('ascii'), hashlib.sha256)
43
44
45class Key:
46    """
47    Encode or decode a key.
48    """
49    separator = ':'
50
51    @enum.unique
52    class Type(enum.Enum):
53        """
54        The type of a key.
55        """
56        private = 'private'
57        public = 'public'
58
59    @staticmethod
60    def decode(encoded_key, expected_type):
61        """
62        Decode a key and check its type if required.
63
64        Arguments:
65            - `encoded_key`: The encoded key.
66            - `expected_type`: One of the types of :class:`Key.Type`.
67
68        Return the key as an :class:`libnacl.public.SecretKey` or
69        :class:`libnacl.public.PublicKey` instance.
70        """
71        # Split key
72        try:
73            type_, key = encoded_key.split(Key.separator)
74        except ValueError as exc:
75            raise GatewayKeyError('Invalid key format') from exc
76        type_ = Key.Type(type_)
77
78        # Check type
79        if type_ != expected_type:
80            raise GatewayKeyError('Invalid key type: {}, expected: {}'.format(
81                type_, expected_type
82            ))
83
84        # De-hexlify
85        key = libnacl.encode.hex_decode(key)
86
87        # Convert to SecretKey or PublicKey
88        if type_ == Key.Type.private:
89            key = libnacl.public.SecretKey(key)
90        elif type_ == Key.Type.public:
91            key = libnacl.public.PublicKey(key)
92
93        return key
94
95    @staticmethod
96    def encode(libnacl_key):
97        """
98        Encode a key.
99
100        Arguments:
101            - `libnacl_key`: An instance of either a
102              :class:`libnacl.public.SecretKey` or a
103              :class:`libnacl.public.PublicKey`.
104
105        Return the encoded key.
106        """
107        # Detect key type and hexlify
108        if isinstance(libnacl_key, libnacl.public.SecretKey):
109            type_ = Key.Type.private
110            key = libnacl_key.hex_sk()
111        elif isinstance(libnacl_key, libnacl.public.PublicKey):
112            type_ = Key.Type.public
113            key = libnacl.encode.hex_encode(libnacl_key.pk)
114        else:
115            raise GatewayKeyError('Unknown key type: {}'.format(libnacl_key))
116
117        # Encode key
118        return Key.separator.join((type_.value, key.decode('utf-8')))
119
120    @staticmethod
121    def generate_pair():
122        """
123        Generate a new key pair.
124
125        Return the key pair as a tuple of a
126        :class:`libnacl.public.SecretKey` instance and a
127        :class:`libnacl.public.PublicKey` instance.
128        """
129        private_key = libnacl.public.SecretKey()
130        public_key = libnacl.public.PublicKey(private_key.pk)
131        return private_key, public_key
132
133    @staticmethod
134    def generate_secret_key():
135        """
136        Generate a new secret key box.
137
138        Return a tuple of the key's :class:`bytes` and hex-encoded
139        representation.
140        """
141        box = libnacl.secret.SecretBox()
142        return box.sk, box.hex_sk()
143
144    @staticmethod
145    def derive_public(private_key):
146        """
147        Derive a public key from a class:`libnacl.public.SecretKey`
148        instance.
149
150        Arguments:
151            - `private_key`: A class:`libnacl.public.SecretKey`
152              instance.
153
154        Return the :class:`libnacl.public.PublicKey` instance.
155        """
156        return libnacl.public.PublicKey(private_key.pk)
157