Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/cryptography/hazmat/primitives/ciphers/aead.py: 80%

122 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 07:26 +0000

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 annotations 

6 

7import os 

8 

9from cryptography import exceptions, utils 

10from cryptography.hazmat.backends.openssl import aead 

11from cryptography.hazmat.backends.openssl.backend import backend 

12from cryptography.hazmat.bindings._rust import FixedPool 

13from cryptography.hazmat.bindings._rust import openssl as rust_openssl 

14 

15__all__ = [ 

16 "ChaCha20Poly1305", 

17 "AESCCM", 

18 "AESGCM", 

19 "AESGCMSIV", 

20 "AESOCB3", 

21 "AESSIV", 

22] 

23 

24AESSIV = rust_openssl.aead.AESSIV 

25AESOCB3 = rust_openssl.aead.AESOCB3 

26AESGCMSIV = rust_openssl.aead.AESGCMSIV 

27 

28 

29class ChaCha20Poly1305: 

30 _MAX_SIZE = 2**31 - 1 

31 

32 def __init__(self, key: bytes): 

33 if not backend.aead_cipher_supported(self): 

34 raise exceptions.UnsupportedAlgorithm( 

35 "ChaCha20Poly1305 is not supported by this version of OpenSSL", 

36 exceptions._Reasons.UNSUPPORTED_CIPHER, 

37 ) 

38 utils._check_byteslike("key", key) 

39 

40 if len(key) != 32: 

41 raise ValueError("ChaCha20Poly1305 key must be 32 bytes.") 

42 

43 self._key = key 

44 self._pool = FixedPool(self._create_fn) 

45 

46 @classmethod 

47 def generate_key(cls) -> bytes: 

48 return os.urandom(32) 

49 

50 def _create_fn(self): 

51 return aead._aead_create_ctx(backend, self, self._key) 

52 

53 def encrypt( 

54 self, 

55 nonce: bytes, 

56 data: bytes, 

57 associated_data: bytes | None, 

58 ) -> bytes: 

59 if associated_data is None: 

60 associated_data = b"" 

61 

62 if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE: 

63 # This is OverflowError to match what cffi would raise 

64 raise OverflowError( 

65 "Data or associated data too long. Max 2**31 - 1 bytes" 

66 ) 

67 

68 self._check_params(nonce, data, associated_data) 

69 with self._pool.acquire() as ctx: 

70 return aead._encrypt( 

71 backend, self, nonce, data, [associated_data], 16, ctx 

72 ) 

73 

74 def decrypt( 

75 self, 

76 nonce: bytes, 

77 data: bytes, 

78 associated_data: bytes | None, 

79 ) -> bytes: 

80 if associated_data is None: 

81 associated_data = b"" 

82 

83 self._check_params(nonce, data, associated_data) 

84 with self._pool.acquire() as ctx: 

85 return aead._decrypt( 

86 backend, self, nonce, data, [associated_data], 16, ctx 

87 ) 

88 

89 def _check_params( 

90 self, 

91 nonce: bytes, 

92 data: bytes, 

93 associated_data: bytes, 

94 ) -> None: 

95 utils._check_byteslike("nonce", nonce) 

96 utils._check_byteslike("data", data) 

97 utils._check_byteslike("associated_data", associated_data) 

98 if len(nonce) != 12: 

99 raise ValueError("Nonce must be 12 bytes") 

100 

101 

102class AESCCM: 

103 _MAX_SIZE = 2**31 - 1 

104 

105 def __init__(self, key: bytes, tag_length: int = 16): 

106 utils._check_byteslike("key", key) 

107 if len(key) not in (16, 24, 32): 

108 raise ValueError("AESCCM key must be 128, 192, or 256 bits.") 

109 

110 self._key = key 

111 if not isinstance(tag_length, int): 

112 raise TypeError("tag_length must be an integer") 

113 

114 if tag_length not in (4, 6, 8, 10, 12, 14, 16): 

115 raise ValueError("Invalid tag_length") 

116 

117 self._tag_length = tag_length 

118 

119 if not backend.aead_cipher_supported(self): 

120 raise exceptions.UnsupportedAlgorithm( 

121 "AESCCM is not supported by this version of OpenSSL", 

122 exceptions._Reasons.UNSUPPORTED_CIPHER, 

123 ) 

124 

125 @classmethod 

126 def generate_key(cls, bit_length: int) -> bytes: 

127 if not isinstance(bit_length, int): 

128 raise TypeError("bit_length must be an integer") 

129 

130 if bit_length not in (128, 192, 256): 

131 raise ValueError("bit_length must be 128, 192, or 256") 

132 

133 return os.urandom(bit_length // 8) 

134 

135 def encrypt( 

136 self, 

137 nonce: bytes, 

138 data: bytes, 

139 associated_data: bytes | None, 

140 ) -> bytes: 

141 if associated_data is None: 

142 associated_data = b"" 

143 

144 if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE: 

145 # This is OverflowError to match what cffi would raise 

146 raise OverflowError( 

147 "Data or associated data too long. Max 2**31 - 1 bytes" 

148 ) 

149 

150 self._check_params(nonce, data, associated_data) 

151 self._validate_lengths(nonce, len(data)) 

152 return aead._encrypt( 

153 backend, self, nonce, data, [associated_data], self._tag_length 

154 ) 

155 

156 def decrypt( 

157 self, 

158 nonce: bytes, 

159 data: bytes, 

160 associated_data: bytes | None, 

161 ) -> bytes: 

162 if associated_data is None: 

163 associated_data = b"" 

164 

165 self._check_params(nonce, data, associated_data) 

166 return aead._decrypt( 

167 backend, self, nonce, data, [associated_data], self._tag_length 

168 ) 

169 

170 def _validate_lengths(self, nonce: bytes, data_len: int) -> None: 

171 # For information about computing this, see 

172 # https://tools.ietf.org/html/rfc3610#section-2.1 

173 l_val = 15 - len(nonce) 

174 if 2 ** (8 * l_val) < data_len: 

175 raise ValueError("Data too long for nonce") 

176 

177 def _check_params( 

178 self, nonce: bytes, data: bytes, associated_data: bytes 

179 ) -> None: 

180 utils._check_byteslike("nonce", nonce) 

181 utils._check_byteslike("data", data) 

182 utils._check_byteslike("associated_data", associated_data) 

183 if not 7 <= len(nonce) <= 13: 

184 raise ValueError("Nonce must be between 7 and 13 bytes") 

185 

186 

187class AESGCM: 

188 _MAX_SIZE = 2**31 - 1 

189 

190 def __init__(self, key: bytes): 

191 utils._check_byteslike("key", key) 

192 if len(key) not in (16, 24, 32): 

193 raise ValueError("AESGCM key must be 128, 192, or 256 bits.") 

194 

195 self._key = key 

196 

197 @classmethod 

198 def generate_key(cls, bit_length: int) -> bytes: 

199 if not isinstance(bit_length, int): 

200 raise TypeError("bit_length must be an integer") 

201 

202 if bit_length not in (128, 192, 256): 

203 raise ValueError("bit_length must be 128, 192, or 256") 

204 

205 return os.urandom(bit_length // 8) 

206 

207 def encrypt( 

208 self, 

209 nonce: bytes, 

210 data: bytes, 

211 associated_data: bytes | None, 

212 ) -> bytes: 

213 if associated_data is None: 

214 associated_data = b"" 

215 

216 if len(data) > self._MAX_SIZE or len(associated_data) > self._MAX_SIZE: 

217 # This is OverflowError to match what cffi would raise 

218 raise OverflowError( 

219 "Data or associated data too long. Max 2**31 - 1 bytes" 

220 ) 

221 

222 self._check_params(nonce, data, associated_data) 

223 return aead._encrypt(backend, self, nonce, data, [associated_data], 16) 

224 

225 def decrypt( 

226 self, 

227 nonce: bytes, 

228 data: bytes, 

229 associated_data: bytes | None, 

230 ) -> bytes: 

231 if associated_data is None: 

232 associated_data = b"" 

233 

234 self._check_params(nonce, data, associated_data) 

235 return aead._decrypt(backend, self, nonce, data, [associated_data], 16) 

236 

237 def _check_params( 

238 self, 

239 nonce: bytes, 

240 data: bytes, 

241 associated_data: bytes, 

242 ) -> None: 

243 utils._check_byteslike("nonce", nonce) 

244 utils._check_byteslike("data", data) 

245 utils._check_byteslike("associated_data", associated_data) 

246 if len(nonce) < 8 or len(nonce) > 128: 

247 raise ValueError("Nonce must be between 8 and 128 bytes")