1# This file is dual licensed under the terms of the Apache License, Version
2# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3# for complete details.
4
5from __future__ import absolute_import, division, print_function
6
7import os
8
9from cryptography import exceptions, utils
10from cryptography.hazmat.backends.openssl import aead
11from cryptography.hazmat.backends.openssl.backend import backend
12
13
14class ChaCha20Poly1305(object):
15    _MAX_SIZE = 2 ** 32
16
17    def __init__(self, key):
18        if not backend.aead_cipher_supported(self):
19            raise exceptions.UnsupportedAlgorithm(
20                "ChaCha20Poly1305 is not supported by this version of OpenSSL",
21                exceptions._Reasons.UNSUPPORTED_CIPHER
22            )
23        utils._check_byteslike("key", key)
24
25        if len(key) != 32:
26            raise ValueError("ChaCha20Poly1305 key must be 32 bytes.")
27
28        self._key = key
29
30    @classmethod
31    def generate_key(cls):
32        return os.urandom(32)
33
34    def encrypt(self, nonce, data, associated_data):
35        if associated_data is None:
36            associated_data = b""
37
38        if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE:
39            # This is OverflowError to match what cffi would raise
40            raise OverflowError(
41                "Data or associated data too long. Max 2**32 bytes"
42            )
43
44        self._check_params(nonce, data, associated_data)
45        return aead._encrypt(
46            backend, self, nonce, data, associated_data, 16
47        )
48
49    def decrypt(self, nonce, data, associated_data):
50        if associated_data is None:
51            associated_data = b""
52
53        self._check_params(nonce, data, associated_data)
54        return aead._decrypt(
55            backend, self, nonce, data, associated_data, 16
56        )
57
58    def _check_params(self, nonce, data, associated_data):
59        utils._check_byteslike("nonce", nonce)
60        utils._check_bytes("data", data)
61        utils._check_bytes("associated_data", associated_data)
62        if len(nonce) != 12:
63            raise ValueError("Nonce must be 12 bytes")
64
65
66class AESCCM(object):
67    _MAX_SIZE = 2 ** 32
68
69    def __init__(self, key, tag_length=16):
70        utils._check_byteslike("key", key)
71        if len(key) not in (16, 24, 32):
72            raise ValueError("AESCCM key must be 128, 192, or 256 bits.")
73
74        self._key = key
75        if not isinstance(tag_length, int):
76            raise TypeError("tag_length must be an integer")
77
78        if tag_length not in (4, 6, 8, 10, 12, 14, 16):
79            raise ValueError("Invalid tag_length")
80
81        self._tag_length = tag_length
82
83        if not backend.aead_cipher_supported(self):
84            raise exceptions.UnsupportedAlgorithm(
85                "AESCCM is not supported by this version of OpenSSL",
86                exceptions._Reasons.UNSUPPORTED_CIPHER
87            )
88
89    @classmethod
90    def generate_key(cls, bit_length):
91        if not isinstance(bit_length, int):
92            raise TypeError("bit_length must be an integer")
93
94        if bit_length not in (128, 192, 256):
95            raise ValueError("bit_length must be 128, 192, or 256")
96
97        return os.urandom(bit_length // 8)
98
99    def encrypt(self, nonce, data, associated_data):
100        if associated_data is None:
101            associated_data = b""
102
103        if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE:
104            # This is OverflowError to match what cffi would raise
105            raise OverflowError(
106                "Data or associated data too long. Max 2**32 bytes"
107            )
108
109        self._check_params(nonce, data, associated_data)
110        self._validate_lengths(nonce, len(data))
111        return aead._encrypt(
112            backend, self, nonce, data, associated_data, self._tag_length
113        )
114
115    def decrypt(self, nonce, data, associated_data):
116        if associated_data is None:
117            associated_data = b""
118
119        self._check_params(nonce, data, associated_data)
120        return aead._decrypt(
121            backend, self, nonce, data, associated_data, self._tag_length
122        )
123
124    def _validate_lengths(self, nonce, data_len):
125        # For information about computing this, see
126        # https://tools.ietf.org/html/rfc3610#section-2.1
127        l_val = 15 - len(nonce)
128        if 2 ** (8 * l_val) < data_len:
129            raise ValueError("Nonce too long for data")
130
131    def _check_params(self, nonce, data, associated_data):
132        utils._check_byteslike("nonce", nonce)
133        utils._check_bytes("data", data)
134        utils._check_bytes("associated_data", associated_data)
135        if not 7 <= len(nonce) <= 13:
136            raise ValueError("Nonce must be between 7 and 13 bytes")
137
138
139class AESGCM(object):
140    _MAX_SIZE = 2 ** 32
141
142    def __init__(self, key):
143        utils._check_byteslike("key", key)
144        if len(key) not in (16, 24, 32):
145            raise ValueError("AESGCM key must be 128, 192, or 256 bits.")
146
147        self._key = key
148
149    @classmethod
150    def generate_key(cls, bit_length):
151        if not isinstance(bit_length, int):
152            raise TypeError("bit_length must be an integer")
153
154        if bit_length not in (128, 192, 256):
155            raise ValueError("bit_length must be 128, 192, or 256")
156
157        return os.urandom(bit_length // 8)
158
159    def encrypt(self, nonce, data, associated_data):
160        if associated_data is None:
161            associated_data = b""
162
163        if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE:
164            # This is OverflowError to match what cffi would raise
165            raise OverflowError(
166                "Data or associated data too long. Max 2**32 bytes"
167            )
168
169        self._check_params(nonce, data, associated_data)
170        return aead._encrypt(
171            backend, self, nonce, data, associated_data, 16
172        )
173
174    def decrypt(self, nonce, data, associated_data):
175        if associated_data is None:
176            associated_data = b""
177
178        self._check_params(nonce, data, associated_data)
179        return aead._decrypt(
180            backend, self, nonce, data, associated_data, 16
181        )
182
183    def _check_params(self, nonce, data, associated_data):
184        utils._check_byteslike("nonce", nonce)
185        utils._check_bytes("data", data)
186        utils._check_bytes("associated_data", associated_data)
187        if len(nonce) == 0:
188            raise ValueError("Nonce must be at least 1 byte")
189