blob: 1c6cb5ddd58ae0b11886d0c2280ded4754084afa [file] [log] [blame]
Alex Gaynor8912d3a2013-11-02 14:04:19 -07001# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10# implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
Alex Gaynor02fad002013-10-30 14:16:13 -070014import base64
Alex Gaynor69ab59e2013-10-31 15:26:43 -070015import binascii
Alex Gaynor02fad002013-10-30 14:16:13 -070016import os
17import struct
18import time
19
Alex Gaynor3d5041c2013-10-31 15:55:33 -070020import cffi
21
Alex Gaynorbbeba712013-10-30 14:29:58 -070022import six
23
Alex Gaynor02fad002013-10-30 14:16:13 -070024from cryptography.hazmat.primitives import padding, hashes
25from cryptography.hazmat.primitives.hmac import HMAC
Alex Gaynordcc3f662013-11-07 14:28:16 -080026from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
Alex Gaynor02fad002013-10-30 14:16:13 -070027
28
Alex Gaynor38f34552013-10-31 14:50:00 -070029class InvalidToken(Exception):
30 pass
31
32
Alex Gaynor286433d2013-11-18 10:15:20 -080033_ffi = cffi.FFI()
34_ffi.cdef("""
35bool Cryptography_constant_time_compare(uint8_t *, size_t, uint8_t *, size_t);
Alex Gaynor3d5041c2013-10-31 15:55:33 -070036""")
Alex Gaynor286433d2013-11-18 10:15:20 -080037_lib = _ffi.verify("""
Alex Gaynor3d5041c2013-10-31 15:55:33 -070038#include <stdbool.h>
39
Alex Gaynor286433d2013-11-18 10:15:20 -080040bool Cryptography_constant_time_compare(uint8_t *a, size_t len_a, uint8_t *b,
41 size_t len_b) {
Alex Gaynor38db2142013-10-31 16:25:25 -070042 size_t i = 0;
Alex Gaynor286433d2013-11-18 10:15:20 -080043 uint8_t mismatch = 0;
Alex Gaynor3d5041c2013-10-31 15:55:33 -070044 if (len_a != len_b) {
45 return false;
46 }
Alex Gaynor38db2142013-10-31 16:25:25 -070047 for (i = 0; i < len_a; i++) {
Alex Gaynor286433d2013-11-18 10:15:20 -080048 mismatch |= a[i] ^ b[i];
Alex Gaynor3d5041c2013-10-31 15:55:33 -070049 }
Alex Gaynor286433d2013-11-18 10:15:20 -080050
51 /* Make sure any bits set are copied to the lowest bit */
52 mismatch |= mismatch >> 4;
53 mismatch |= mismatch >> 2;
54 mismatch |= mismatch >> 1;
55 /* Now check the low bit to see if it's set */
56 return (mismatch & 1) == 0;
Alex Gaynor3d5041c2013-10-31 15:55:33 -070057}
58""")
59
Alex Gaynor2c58bbe2013-10-31 16:31:38 -070060
Alex Gaynor02fad002013-10-30 14:16:13 -070061class Fernet(object):
Alex Gaynor105e8132013-11-07 14:25:42 -080062 def __init__(self, key, backend=None):
Alex Gaynor02fad002013-10-30 14:16:13 -070063 super(Fernet, self).__init__()
Alex Gaynor898fe0f2013-11-20 16:38:32 -080064 key = base64.urlsafe_b64decode(key)
Alex Gaynorcd47c4a2013-10-31 09:46:27 -070065 assert len(key) == 32
Alex Gaynor02fad002013-10-30 14:16:13 -070066 self.signing_key = key[:16]
67 self.encryption_key = key[16:]
Alex Gaynor105e8132013-11-07 14:25:42 -080068 self.backend = backend
Alex Gaynor02fad002013-10-30 14:16:13 -070069
70 def encrypt(self, data):
71 current_time = int(time.time())
72 iv = os.urandom(16)
73 return self._encrypt_from_parts(data, current_time, iv)
74
75 def _encrypt_from_parts(self, data, current_time, iv):
Alex Gaynor38f34552013-10-31 14:50:00 -070076 if isinstance(data, six.text_type):
Alex Gaynorc1ea0a02013-10-31 15:03:53 -070077 raise TypeError(
78 "Unicode-objects must be encoded before encryption"
79 )
Alex Gaynor38f34552013-10-31 14:50:00 -070080
Alex Gaynordcc3f662013-11-07 14:28:16 -080081 padder = padding.PKCS7(algorithms.AES.block_size).padder()
Alex Gaynor02fad002013-10-30 14:16:13 -070082 padded_data = padder.update(data) + padder.finalize()
Alex Gaynordcc3f662013-11-07 14:28:16 -080083 encryptor = Cipher(
84 algorithms.AES(self.encryption_key), modes.CBC(iv), self.backend
Alex Gaynorde36e902013-10-31 10:10:44 -070085 ).encryptor()
Alex Gaynor02fad002013-10-30 14:16:13 -070086 ciphertext = encryptor.update(padded_data) + encryptor.finalize()
87
Alex Gaynor1d2901c2013-11-22 10:12:05 -080088 basic_parts = (
89 b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext
Alex Gaynor02fad002013-10-30 14:16:13 -070090 )
Alex Gaynorbbeba712013-10-30 14:29:58 -070091
Alex Gaynor1d2901c2013-11-22 10:12:05 -080092 h = HMAC(self.signing_key, hashes.SHA256(), self.backend)
93 h.update(basic_parts)
94 hmac = h.finalize()
95 return base64.urlsafe_b64encode(basic_parts + hmac)
96
97 def decrypt(self, data, ttl=None):
Alex Gaynor38f34552013-10-31 14:50:00 -070098 if isinstance(data, six.text_type):
Alex Gaynorc1ea0a02013-10-31 15:03:53 -070099 raise TypeError(
100 "Unicode-objects must be encoded before decryption"
101 )
Alex Gaynor38f34552013-10-31 14:50:00 -0700102
Alex Gaynor1d2901c2013-11-22 10:12:05 -0800103 current_time = int(time.time())
Alex Gaynor38f34552013-10-31 14:50:00 -0700104
105 try:
106 data = base64.urlsafe_b64decode(data)
Alex Gaynor69ab59e2013-10-31 15:26:43 -0700107 except (TypeError, binascii.Error):
Alex Gaynor38f34552013-10-31 14:50:00 -0700108 raise InvalidToken
109
Alex Gaynor5c5342e2013-10-31 11:25:54 -0700110 assert six.indexbytes(data, 0) == 0x80
Alex Gaynorf5938482013-10-30 14:34:55 -0700111 timestamp = data[1:9]
112 iv = data[9:25]
113 ciphertext = data[25:-32]
Alex Gaynorbbeba712013-10-30 14:29:58 -0700114 if ttl is not None:
Alex Gaynor5e87dfd2013-10-31 09:46:03 -0700115 if struct.unpack(">Q", timestamp)[0] + ttl < current_time:
Alex Gaynor38f34552013-10-31 14:50:00 -0700116 raise InvalidToken
Alex Gaynor105e8132013-11-07 14:25:42 -0800117 h = HMAC(self.signing_key, hashes.SHA256(), self.backend)
Alex Gaynorbbeba712013-10-30 14:29:58 -0700118 h.update(data[:-32])
Alex Gaynorfa7081a2013-11-01 16:38:25 -0700119 hmac = h.finalize()
Alex Gaynor286433d2013-11-18 10:15:20 -0800120 valid = _lib.Cryptography_constant_time_compare(
121 hmac, len(hmac), data[-32:], 32
122 )
123 if not valid:
Alex Gaynor38f34552013-10-31 14:50:00 -0700124 raise InvalidToken
125
Alex Gaynordcc3f662013-11-07 14:28:16 -0800126 decryptor = Cipher(
127 algorithms.AES(self.encryption_key), modes.CBC(iv), self.backend
Alex Gaynorde36e902013-10-31 10:10:44 -0700128 ).decryptor()
Alex Gaynor2b21b122013-10-31 09:39:25 -0700129 plaintext_padded = decryptor.update(ciphertext) + decryptor.finalize()
Alex Gaynordcc3f662013-11-07 14:28:16 -0800130 unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
Alex Gaynor38f34552013-10-31 14:50:00 -0700131
132 unpadded = unpadder.update(plaintext_padded)
133 try:
134 unpadded += unpadder.finalize()
135 except ValueError:
136 raise InvalidToken
137 return unpadded