1# This file is part of Scapy
2# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard
3#               2015, 2016, 2017 Maxence Tury
4# This program is published under a GPLv2 license
5
6"""
7Stream ciphers.
8"""
9
10from __future__ import absolute_import
11from scapy.config import conf
12from scapy.layers.tls.crypto.common import CipherError
13import scapy.modules.six as six
14
15if conf.crypto_valid:
16    from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
17    from cryptography.hazmat.backends import default_backend
18
19
20_tls_stream_cipher_algs = {}
21
22
23class _StreamCipherMetaclass(type):
24    """
25    Cipher classes are automatically registered through this metaclass.
26    Furthermore, their name attribute is extracted from their class name.
27    """
28    def __new__(cls, ciph_name, bases, dct):
29        if ciph_name != "_StreamCipher":
30            dct["name"] = ciph_name[7:]     # remove leading "Cipher_"
31        the_class = super(_StreamCipherMetaclass, cls).__new__(cls, ciph_name,
32                                                               bases, dct)
33        if ciph_name != "_StreamCipher":
34            _tls_stream_cipher_algs[ciph_name[7:]] = the_class
35        return the_class
36
37
38class _StreamCipher(six.with_metaclass(_StreamCipherMetaclass, object)):
39    type = "stream"
40
41    def __init__(self, key=None):
42        """
43        Note that we have to keep the encryption/decryption state in unique
44        encryptor and decryptor objects. This differs from _BlockCipher.
45
46        In order to do connection state snapshots, we need to be able to
47        recreate past cipher contexts. This is why we feed _enc_updated_with
48        and _dec_updated_with every time encrypt() or decrypt() is called.
49        """
50        self.ready = {"key": True}
51        if key is None:
52            self.ready["key"] = False
53            if hasattr(self, "expanded_key_len"):
54                tmp_len = self.expanded_key_len
55            else:
56                tmp_len = self.key_len
57            key = b"\0" * tmp_len
58
59        # we use super() in order to avoid any deadlock with __setattr__
60        super(_StreamCipher, self).__setattr__("key", key)
61
62        self._cipher = Cipher(self.pc_cls(key),
63                              mode=None,
64                              backend=default_backend())
65        self.encryptor = self._cipher.encryptor()
66        self.decryptor = self._cipher.decryptor()
67        self._enc_updated_with = b""
68        self._dec_updated_with = b""
69
70    def __setattr__(self, name, val):
71        """
72        We have to keep the encryptor/decryptor for a long time,
73        however they have to be updated every time the key is changed.
74        """
75        if name == "key":
76            if self._cipher is not None:
77                self._cipher.algorithm.key = val
78                self.encryptor = self._cipher.encryptor()
79                self.decryptor = self._cipher.decryptor()
80            self.ready["key"] = True
81        super(_StreamCipher, self).__setattr__(name, val)
82
83    def encrypt(self, data):
84        if False in six.itervalues(self.ready):
85            raise CipherError(data)
86        self._enc_updated_with += data
87        return self.encryptor.update(data)
88
89    def decrypt(self, data):
90        if False in six.itervalues(self.ready):
91            raise CipherError(data)
92        self._dec_updated_with += data
93        return self.decryptor.update(data)
94
95    def snapshot(self):
96        c = self.__class__(self.key)
97        c.ready = self.ready.copy()
98        c.encryptor.update(self._enc_updated_with)
99        c.decryptor.update(self._dec_updated_with)
100        c._enc_updated_with = self._enc_updated_with
101        c._dec_updated_with = self._dec_updated_with
102        return c
103
104
105if conf.crypto_valid:
106    class Cipher_RC4_128(_StreamCipher):
107        pc_cls = algorithms.ARC4
108        key_len = 16
109
110    class Cipher_RC4_40(Cipher_RC4_128):
111        expanded_key_len = 16
112        key_len = 5
113
114
115class Cipher_NULL(_StreamCipher):
116    key_len = 0
117
118    def __init__(self, key=None):
119        self.ready = {"key": True}
120        self._cipher = None
121        # we use super() in order to avoid any deadlock with __setattr__
122        super(Cipher_NULL, self).__setattr__("key", key)
123
124    def snapshot(self):
125        c = self.__class__(self.key)
126        c.ready = self.ready.copy()
127        return c
128
129    def encrypt(self, data):
130        return data
131
132    def decrypt(self, data):
133        return data
134